@eeacms/volto-bise-policy 1.2.32 → 1.2.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/package.json +3 -1
- package/src/components/Widgets/GeolocationWidget.jsx +143 -0
- package/src/components/Widgets/GeolocationWidgetMapContainer.jsx +131 -0
- package/src/components/Widgets/NRRWidgets.jsx +95 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyExplorerEdit.jsx +5 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyExplorerView.jsx +107 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyExplorerView.test.jsx +89 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyFilters.jsx +339 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyFilters.test.jsx +111 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyListing.jsx +330 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyListing.test.jsx +166 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyMap.jsx +237 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyMap.test.jsx +176 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/FeatureDisplay.jsx +41 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/FeatureDisplay.test.jsx +32 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/FeatureInteraction.jsx +98 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/FeatureInteraction.test.jsx +160 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/InfoOverlay.jsx +82 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/InfoOverlay.test.jsx +153 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/hooks.js +20 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/images/icon-depth.png +0 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/images/icon-light.png +0 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/images/search.svg +3 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/index.js +16 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/mockJsdom.js +8 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/styles.less +359 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/styles.less_old +201 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/utils.js +144 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/utils.test.js +88 -0
- package/src/components/manage/Blocks/index.js +2 -0
- package/src/express-middleware.js +37 -0
- package/src/index.js +29 -0
- package/theme/globals/site.overrides +12 -4
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,40 @@ All notable changes to this project will be documented in this file. Dates are d
|
|
|
4
4
|
|
|
5
5
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
6
6
|
|
|
7
|
+
### [1.2.34](https://github.com/eea/volto-bise-policy/compare/1.2.33...1.2.34) - 23 January 2026
|
|
8
|
+
|
|
9
|
+
#### :rocket: New Features
|
|
10
|
+
|
|
11
|
+
- feat: added NRR measure view widgets [laszlocseh - [`9468505`](https://github.com/eea/volto-bise-policy/commit/9468505f30d0755de0e2c3386e63229244137777)]
|
|
12
|
+
- feat: small color changes to case study map [laszlocseh - [`4cba979`](https://github.com/eea/volto-bise-policy/commit/4cba979d7292210659ca60f96e438a646aa94fab)]
|
|
13
|
+
- feat: small color changes to case study map [laszlocseh - [`ca6f43c`](https://github.com/eea/volto-bise-policy/commit/ca6f43c5cc81f16568650f2dfdca2591348bc75a)]
|
|
14
|
+
- feat: added GeolocationWidget for case studies [laszlocseh - [`17b6c1c`](https://github.com/eea/volto-bise-policy/commit/17b6c1c72c336c8c87291dc4303f02bcd82e7956)]
|
|
15
|
+
- feat: NRR case study map [laszlocseh - [`daafb50`](https://github.com/eea/volto-bise-policy/commit/daafb50825d893da08d64fdd48a9b287ec9ff916)]
|
|
16
|
+
|
|
17
|
+
#### :nail_care: Enhancements
|
|
18
|
+
|
|
19
|
+
- change: added typology_of_measures filter to case study map [laszlocseh - [`cd9d50e`](https://github.com/eea/volto-bise-policy/commit/cd9d50e9fefab49ddbf80c4b61113ec0beaf9b73)]
|
|
20
|
+
- change: added measures filter to case study map [laszlocseh - [`0a6bcdd`](https://github.com/eea/volto-bise-policy/commit/0a6bcdde399d2354e112a25ef3abe5cbd8f099cb)]
|
|
21
|
+
|
|
22
|
+
#### :house: Internal changes
|
|
23
|
+
|
|
24
|
+
- style: Automated code fix [eea-jenkins - [`e10917b`](https://github.com/eea/volto-bise-policy/commit/e10917b4a14a0897707fd50d33913e969d46ee89)]
|
|
25
|
+
- style: Automated code fix [eea-jenkins - [`702f53d`](https://github.com/eea/volto-bise-policy/commit/702f53d74ff560b58a48bf519fc9a40cb9bddb56)]
|
|
26
|
+
- style: Automated code fix [eea-jenkins - [`1b2648b`](https://github.com/eea/volto-bise-policy/commit/1b2648bedc44c4ca955499cb3267104e40e0a9e9)]
|
|
27
|
+
- style: Automated code fix [eea-jenkins - [`87e5dff`](https://github.com/eea/volto-bise-policy/commit/87e5dffcf2e7f362e095fc20b54e932f186bbcf8)]
|
|
28
|
+
|
|
29
|
+
#### :hammer_and_wrench: Others
|
|
30
|
+
|
|
31
|
+
- test: fix estlint and tests [laszlocseh - [`8867193`](https://github.com/eea/volto-bise-policy/commit/88671932c76859406c397eb9cb108ce4073f7952)]
|
|
32
|
+
- test: fix case study map tests [laszlocseh - [`8459b83`](https://github.com/eea/volto-bise-policy/commit/8459b839766a8fda4c729c558cb6ce940f1c8536)]
|
|
33
|
+
- fix eslint [laszlocseh - [`8cd8dfa`](https://github.com/eea/volto-bise-policy/commit/8cd8dfabd3ffaf49eb0b8fe5dec076f61e48a6ac)]
|
|
34
|
+
- added volto-openlayers-map to package.json [laszlocseh - [`2d9eb10`](https://github.com/eea/volto-bise-policy/commit/2d9eb101c4ce5fd490560e2a91c025a85a8474cb)]
|
|
35
|
+
### [1.2.33](https://github.com/eea/volto-bise-policy/compare/1.2.32...1.2.33) - 9 December 2025
|
|
36
|
+
|
|
37
|
+
#### :bug: Bug Fixes
|
|
38
|
+
|
|
39
|
+
- fix: wrong z-index for the menu in some edge cases [laszlocseh - [`dc23db7`](https://github.com/eea/volto-bise-policy/commit/dc23db7a2f10ae3c64cb25835dbdda30b6f1e3e4)]
|
|
40
|
+
|
|
7
41
|
### [1.2.32](https://github.com/eea/volto-bise-policy/compare/1.2.31...1.2.32) - 3 December 2025
|
|
8
42
|
|
|
9
43
|
#### :nail_care: Enhancements
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eeacms/volto-bise-policy",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.34",
|
|
4
4
|
"description": "@eeacms/volto-bise-policy: Volto add-on",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"author": "European Environment Agency: IDM2 A-Team",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"react"
|
|
14
14
|
],
|
|
15
15
|
"addons": [
|
|
16
|
+
"@eeacms/volto-openlayers-map",
|
|
16
17
|
"@eeacms/volto-datablocks",
|
|
17
18
|
"@eeacms/volto-block-toc",
|
|
18
19
|
"@eeacms/volto-eea-design-system",
|
|
@@ -29,6 +30,7 @@
|
|
|
29
30
|
"@eeacms/volto-datablocks": "*",
|
|
30
31
|
"@eeacms/volto-eea-design-system": "*",
|
|
31
32
|
"@eeacms/volto-eea-website-theme": "*",
|
|
33
|
+
"@eeacms/volto-openlayers-map": "1.0.1",
|
|
32
34
|
"@eeacms/volto-resize-helper": "*",
|
|
33
35
|
"@plone-collective/volto-authomatic": "2.0.1",
|
|
34
36
|
"esri-loader": "3.7.0",
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { Input, Label, Button } from 'semantic-ui-react';
|
|
4
|
+
import config from '@plone/volto/registry';
|
|
5
|
+
|
|
6
|
+
import { injectIntl } from 'react-intl';
|
|
7
|
+
import { FormFieldWrapper } from '@plone/volto/components';
|
|
8
|
+
import MapContainer from './GeolocationWidgetMapContainer';
|
|
9
|
+
|
|
10
|
+
const defaultValue = {
|
|
11
|
+
latitude: 55.6761,
|
|
12
|
+
longitude: 12.5683,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const GeolocationWidget = (props) => {
|
|
16
|
+
const { id, value, onChange } = props;
|
|
17
|
+
|
|
18
|
+
const [address, setAddress] = useState('');
|
|
19
|
+
const [isFetching, setIsFetching] = useState();
|
|
20
|
+
|
|
21
|
+
const handleAddressChange = (event) => {
|
|
22
|
+
setAddress(event.target.value);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const handleSearch = async (e) => {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
|
|
28
|
+
const url = `https://nominatim.openstreetmap.org/search?q=${address}&format=json`;
|
|
29
|
+
|
|
30
|
+
const { corsProxyPath = '/cors-proxy', host, port } = config.settings;
|
|
31
|
+
const base = __SERVER__
|
|
32
|
+
? `http://${host}:${port}`
|
|
33
|
+
: `${window.location.protocol}//${window.location.host}`;
|
|
34
|
+
|
|
35
|
+
const path = `${base}${corsProxyPath}/${url}`;
|
|
36
|
+
|
|
37
|
+
let locations;
|
|
38
|
+
setIsFetching(true);
|
|
39
|
+
try {
|
|
40
|
+
const response = await fetch(path);
|
|
41
|
+
locations = await response.json();
|
|
42
|
+
setIsFetching(false);
|
|
43
|
+
} catch (e) {
|
|
44
|
+
// eslint-disable-next-line no-console
|
|
45
|
+
console.log('error in fetching location', e);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (locations?.length) {
|
|
49
|
+
const { lat, lon } = locations[0];
|
|
50
|
+
onChange(id, { latitude: lat, longitude: lon });
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (__SERVER__) return '';
|
|
55
|
+
|
|
56
|
+
const lat = value?.latitude ?? defaultValue.latitude;
|
|
57
|
+
const long = value?.longitude ?? defaultValue.longitude;
|
|
58
|
+
const mapKey = `${lat}_${long}`;
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<FormFieldWrapper {...props} className="geolocation-field">
|
|
62
|
+
<div className="ui form">
|
|
63
|
+
<div className="inline fields">
|
|
64
|
+
<div className="field">
|
|
65
|
+
<Input
|
|
66
|
+
type="text"
|
|
67
|
+
value={address}
|
|
68
|
+
onChange={handleAddressChange}
|
|
69
|
+
onKeyDown={(e) => {
|
|
70
|
+
if (e.key === 'Enter') handleSearch(e);
|
|
71
|
+
}}
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
<div className="field">
|
|
75
|
+
<Button onClick={handleSearch}>
|
|
76
|
+
{isFetching ? 'Loading' : 'Search'}
|
|
77
|
+
</Button>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
<MapContainer
|
|
82
|
+
key={mapKey}
|
|
83
|
+
longitude={value?.longitude || defaultValue.longitude}
|
|
84
|
+
latitude={value?.latitude || defaultValue.latitude}
|
|
85
|
+
onChange={({ latitude, longitude }) =>
|
|
86
|
+
onChange(id, { ...value, longitude, latitude })
|
|
87
|
+
}
|
|
88
|
+
/>
|
|
89
|
+
<div className="ui form">
|
|
90
|
+
<div className="inline fields">
|
|
91
|
+
<div className="field">
|
|
92
|
+
<Label>Latitude</Label>
|
|
93
|
+
<Input
|
|
94
|
+
type="number"
|
|
95
|
+
placeholder="latitude"
|
|
96
|
+
value={value?.latitude || defaultValue.latitude}
|
|
97
|
+
onChange={(e) =>
|
|
98
|
+
onChange(id, { ...value, latitude: e.target.value })
|
|
99
|
+
}
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
<div className="field">
|
|
103
|
+
<Label>Longitude</Label>
|
|
104
|
+
<Input
|
|
105
|
+
type="number"
|
|
106
|
+
placeholder="longitude"
|
|
107
|
+
value={value?.longitude || defaultValue.longitude}
|
|
108
|
+
onChange={(e) =>
|
|
109
|
+
onChange(id, { ...value, longitude: e.target.value })
|
|
110
|
+
}
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</FormFieldWrapper>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
GeolocationWidget.propTypes = {
|
|
120
|
+
id: PropTypes.string.isRequired,
|
|
121
|
+
title: PropTypes.string.isRequired,
|
|
122
|
+
description: PropTypes.string,
|
|
123
|
+
required: PropTypes.bool,
|
|
124
|
+
error: PropTypes.arrayOf(PropTypes.string),
|
|
125
|
+
value: PropTypes.object,
|
|
126
|
+
onChange: PropTypes.func,
|
|
127
|
+
onEdit: PropTypes.func,
|
|
128
|
+
onDelete: PropTypes.func,
|
|
129
|
+
wrapped: PropTypes.bool,
|
|
130
|
+
placeholder: PropTypes.string,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
GeolocationWidget.defaultProps = {
|
|
134
|
+
description: null,
|
|
135
|
+
required: false,
|
|
136
|
+
error: [],
|
|
137
|
+
value: null,
|
|
138
|
+
onChange: null,
|
|
139
|
+
onEdit: null,
|
|
140
|
+
onDelete: null,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export default injectIntl(GeolocationWidget);
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { withOpenLayers } from '@eeacms/volto-openlayers-map';
|
|
2
|
+
// import { openlayers as ol } from '@eeacms/volto-openlayers-map';
|
|
3
|
+
import {
|
|
4
|
+
Controls,
|
|
5
|
+
Interactions,
|
|
6
|
+
Layer,
|
|
7
|
+
Layers,
|
|
8
|
+
Map,
|
|
9
|
+
} from '@eeacms/volto-openlayers-map/api';
|
|
10
|
+
import React, { useState } from 'react';
|
|
11
|
+
import { useMapContext } from '@eeacms/volto-openlayers-map/hocs';
|
|
12
|
+
|
|
13
|
+
function PinInteraction({ longitude, latitude, onChange, ol }) {
|
|
14
|
+
const mapContext = useMapContext();
|
|
15
|
+
const { addLayer, addInteraction, map } = mapContext;
|
|
16
|
+
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
if (
|
|
19
|
+
!map ||
|
|
20
|
+
typeof latitude === 'undefined' ||
|
|
21
|
+
typeof longitude === 'undefined'
|
|
22
|
+
)
|
|
23
|
+
return;
|
|
24
|
+
|
|
25
|
+
// Create a feature (the pin) and set it to be draggable
|
|
26
|
+
const pin = new ol.ol.Feature({
|
|
27
|
+
geometry: new ol.geom.Point(ol.proj.fromLonLat([longitude, latitude])), // Initial location
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Style for the pin
|
|
31
|
+
pin.setStyle(
|
|
32
|
+
new ol.style.Style({
|
|
33
|
+
image: new ol.style.Icon({
|
|
34
|
+
anchor: [0.5, 1],
|
|
35
|
+
src: 'https://openlayers.org/en/latest/examples/data/icon.png',
|
|
36
|
+
}),
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// Create a vector layer to hold the pin
|
|
41
|
+
const vectorSource = new ol.source.Vector({
|
|
42
|
+
features: [pin],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const vectorLayer = new ol.layer.Vector({
|
|
46
|
+
source: vectorSource,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
map.addLayer(vectorLayer);
|
|
50
|
+
|
|
51
|
+
// Add drag interaction
|
|
52
|
+
const dragInteraction = new ol.interaction.Modify({
|
|
53
|
+
source: vectorSource,
|
|
54
|
+
pixelTolerance: 20,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
map.addInteraction(dragInteraction);
|
|
58
|
+
|
|
59
|
+
// Log the new position when the pin is dragged
|
|
60
|
+
dragInteraction.on('modifyend', function (event) {
|
|
61
|
+
const feature = event.features.getArray()[0];
|
|
62
|
+
const coordinates = feature.getGeometry().getCoordinates();
|
|
63
|
+
const lonLat = ol.proj.toLonLat(coordinates);
|
|
64
|
+
const [longitude, latitude] = lonLat;
|
|
65
|
+
onChange({ latitude, longitude });
|
|
66
|
+
});
|
|
67
|
+
}, [addInteraction, addLayer, map, onChange, latitude, longitude, ol]);
|
|
68
|
+
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const TileSetLoader = (props) => {
|
|
73
|
+
const [tileWMSSources, setTileWMSSources] = useState([]);
|
|
74
|
+
const { ol } = props;
|
|
75
|
+
|
|
76
|
+
React.useEffect(() => {
|
|
77
|
+
setTileWMSSources([
|
|
78
|
+
new ol.source.TileWMS({
|
|
79
|
+
url: 'https://gisco-services.ec.europa.eu/maps/service',
|
|
80
|
+
params: {
|
|
81
|
+
LAYERS: 'OSMBlossomComposite',
|
|
82
|
+
TILED: true,
|
|
83
|
+
},
|
|
84
|
+
serverType: 'geoserver',
|
|
85
|
+
transition: 0,
|
|
86
|
+
}),
|
|
87
|
+
]);
|
|
88
|
+
}, [ol]);
|
|
89
|
+
|
|
90
|
+
return tileWMSSources ? (
|
|
91
|
+
<MapContainer {...props} source={tileWMSSources[0]} />
|
|
92
|
+
) : null;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const MapContainer = (props) => {
|
|
96
|
+
const { longitude, latitude, source, onChange, ol } = props;
|
|
97
|
+
return (
|
|
98
|
+
<Map
|
|
99
|
+
view={{
|
|
100
|
+
center: ol.proj.fromLonLat([longitude, latitude]),
|
|
101
|
+
showFullExtent: true,
|
|
102
|
+
zoom: 15,
|
|
103
|
+
}}
|
|
104
|
+
pixelRatio={1}
|
|
105
|
+
controls={ol.control.defaults({ attribution: false })}
|
|
106
|
+
>
|
|
107
|
+
<Layers>
|
|
108
|
+
<Controls attribution={false} zoom={false} />
|
|
109
|
+
<PinInteraction
|
|
110
|
+
ol={ol}
|
|
111
|
+
latitude={latitude}
|
|
112
|
+
longitude={longitude}
|
|
113
|
+
onChange={onChange}
|
|
114
|
+
/>
|
|
115
|
+
<Interactions
|
|
116
|
+
doubleClickZoom={true}
|
|
117
|
+
dragAndDrop={true}
|
|
118
|
+
dragPan={true}
|
|
119
|
+
keyboardPan={true}
|
|
120
|
+
keyboardZoom={true}
|
|
121
|
+
mouseWheelZoom={true}
|
|
122
|
+
pointer={true}
|
|
123
|
+
select={true}
|
|
124
|
+
/>
|
|
125
|
+
<Layer.Tile source={source} zIndex={0} />
|
|
126
|
+
</Layers>
|
|
127
|
+
</Map>
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export default withOpenLayers(TileSetLoader);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export const NRRTypologyOfMeasuresView = ({ value }) => {
|
|
2
|
+
let parsedValue = value;
|
|
3
|
+
|
|
4
|
+
if (typeof value === 'string') {
|
|
5
|
+
try {
|
|
6
|
+
parsedValue = JSON.parse(value);
|
|
7
|
+
} catch (e) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const items = Array.isArray(parsedValue)
|
|
13
|
+
? parsedValue
|
|
14
|
+
: parsedValue?.value || [];
|
|
15
|
+
|
|
16
|
+
if (!items || items.length === 0) return null;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="eunis-widget-view">
|
|
20
|
+
<span>
|
|
21
|
+
<b>Related typology of measures: </b>
|
|
22
|
+
</span>
|
|
23
|
+
{items.map((item) => (
|
|
24
|
+
<span key={item['@id']}>
|
|
25
|
+
<b>{item.title.split(' - ')[0]}</b> {item.title.split(' - ')[1]}
|
|
26
|
+
{items.indexOf(item) < items.length - 1 ? ' | ' : ''}
|
|
27
|
+
</span>
|
|
28
|
+
))}
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const NRRArticleView = ({ value }) => {
|
|
34
|
+
let parsedValue = value;
|
|
35
|
+
|
|
36
|
+
if (typeof value === 'string') {
|
|
37
|
+
try {
|
|
38
|
+
parsedValue = JSON.parse(value);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const items = Array.isArray(parsedValue)
|
|
45
|
+
? parsedValue
|
|
46
|
+
: parsedValue?.value || [];
|
|
47
|
+
|
|
48
|
+
if (!items || items.length === 0) return null;
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="eunis-widget-viewZ">
|
|
52
|
+
<span>
|
|
53
|
+
<b>NRR Article: </b>
|
|
54
|
+
</span>
|
|
55
|
+
{items.map((item) => (
|
|
56
|
+
<span key={item['@id']}>
|
|
57
|
+
{item.title.split(' - ')[0].split(' ')[1]}
|
|
58
|
+
{items.indexOf(item) < items.length - 1 ? ', ' : ''}
|
|
59
|
+
</span>
|
|
60
|
+
))}
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const NRREcosystemTypologyView = ({ value }) => {
|
|
66
|
+
let parsedValue = value;
|
|
67
|
+
|
|
68
|
+
if (typeof value === 'string') {
|
|
69
|
+
try {
|
|
70
|
+
parsedValue = JSON.parse(value);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const items = Array.isArray(parsedValue)
|
|
77
|
+
? parsedValue
|
|
78
|
+
: parsedValue?.value || [];
|
|
79
|
+
|
|
80
|
+
if (!items || items.length === 0) return null;
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div className="eunis-widget-view">
|
|
84
|
+
<span>
|
|
85
|
+
<b>Ecosystem type: </b>
|
|
86
|
+
</span>
|
|
87
|
+
{items.map((item) => (
|
|
88
|
+
<span key={item['@id']}>
|
|
89
|
+
{item.title}
|
|
90
|
+
{items.indexOf(item) < items.length - 1 ? ', ' : ''}
|
|
91
|
+
</span>
|
|
92
|
+
))}
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Grid } from 'semantic-ui-react'; // Dropdown,
|
|
3
|
+
import { addAppURL } from '@plone/volto/helpers';
|
|
4
|
+
// import config from '@plone/volto/registry';
|
|
5
|
+
import CaseStudyMap from './CaseStudyMap';
|
|
6
|
+
import { ActiveFilters, CaseStudyFilters, SearchBox } from './CaseStudyFilters';
|
|
7
|
+
// import CaseStudyList from './CaseStudyListing';
|
|
8
|
+
|
|
9
|
+
import { filterCases, getFilters } from './utils';
|
|
10
|
+
import { useCases } from './hooks';
|
|
11
|
+
|
|
12
|
+
import './styles.less';
|
|
13
|
+
|
|
14
|
+
export default function CaseStudyExplorerView(props) {
|
|
15
|
+
const cases_url = '/@@case-studies-map.arcgis.json';
|
|
16
|
+
let cases = useCases(addAppURL(cases_url));
|
|
17
|
+
const { caseStudiesIds } = props; // case studies from measure view
|
|
18
|
+
const [selectedCase, onSelectedCase] = React.useState(null);
|
|
19
|
+
const [searchInput, setSearchInput] = React.useState('');
|
|
20
|
+
const hideFilters = caseStudiesIds ? true : false;
|
|
21
|
+
|
|
22
|
+
const [activeFilters, setActiveFilters] = React.useState({
|
|
23
|
+
measures_implemented: [],
|
|
24
|
+
typology_of_measures: [],
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const [activeItems, setActiveItems] = React.useState(cases);
|
|
28
|
+
const [filters, setFilters] = React.useState([]);
|
|
29
|
+
const [map, setMap] = React.useState();
|
|
30
|
+
|
|
31
|
+
React.useEffect(() => {
|
|
32
|
+
const _filters = getFilters(cases);
|
|
33
|
+
setFilters(_filters);
|
|
34
|
+
}, [
|
|
35
|
+
cases,
|
|
36
|
+
activeFilters.measures_implemented,
|
|
37
|
+
activeFilters.typology_of_measures,
|
|
38
|
+
activeItems.length,
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
React.useEffect(() => {
|
|
42
|
+
let activeItems = filterCases(
|
|
43
|
+
cases,
|
|
44
|
+
activeFilters,
|
|
45
|
+
caseStudiesIds,
|
|
46
|
+
searchInput,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
setActiveItems(activeItems);
|
|
50
|
+
}, [caseStudiesIds, activeFilters, cases, searchInput]);
|
|
51
|
+
|
|
52
|
+
if (__SERVER__) return '';
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div className="searchlib-block">
|
|
56
|
+
<Grid.Row>
|
|
57
|
+
{hideFilters ? null : (
|
|
58
|
+
<SearchBox
|
|
59
|
+
filters={filters}
|
|
60
|
+
activeFilters={activeFilters}
|
|
61
|
+
setActiveFilters={setActiveFilters}
|
|
62
|
+
searchInput={searchInput}
|
|
63
|
+
setSearchInput={setSearchInput}
|
|
64
|
+
map={map}
|
|
65
|
+
/>
|
|
66
|
+
)}
|
|
67
|
+
</Grid.Row>
|
|
68
|
+
<Grid.Row>
|
|
69
|
+
{hideFilters ? null : (
|
|
70
|
+
<ActiveFilters
|
|
71
|
+
filters={filters}
|
|
72
|
+
activeFilters={activeFilters}
|
|
73
|
+
setActiveFilters={setActiveFilters}
|
|
74
|
+
/>
|
|
75
|
+
)}
|
|
76
|
+
</Grid.Row>
|
|
77
|
+
<Grid.Row stretched={true} id="cse-filter">
|
|
78
|
+
{hideFilters ? null : (
|
|
79
|
+
<CaseStudyFilters
|
|
80
|
+
filters={filters}
|
|
81
|
+
activeFilters={activeFilters}
|
|
82
|
+
setActiveFilters={setActiveFilters}
|
|
83
|
+
map={map}
|
|
84
|
+
/>
|
|
85
|
+
)}
|
|
86
|
+
</Grid.Row>
|
|
87
|
+
<Grid.Row>
|
|
88
|
+
{cases.length ? (
|
|
89
|
+
<Grid columns={12}>
|
|
90
|
+
<Grid.Column mobile={12} tablet={12} computer={12}>
|
|
91
|
+
<CaseStudyMap
|
|
92
|
+
items={cases}
|
|
93
|
+
activeItems={activeItems}
|
|
94
|
+
hideFilters={hideFilters}
|
|
95
|
+
selectedCase={selectedCase}
|
|
96
|
+
onSelectedCase={onSelectedCase}
|
|
97
|
+
searchInput={searchInput}
|
|
98
|
+
map={map}
|
|
99
|
+
setMap={setMap}
|
|
100
|
+
/>
|
|
101
|
+
</Grid.Column>
|
|
102
|
+
</Grid>
|
|
103
|
+
) : null}
|
|
104
|
+
</Grid.Row>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom/extend-expect';
|
|
4
|
+
import { useCases } from './hooks';
|
|
5
|
+
import CaseStudyExplorerView from './CaseStudyExplorerView';
|
|
6
|
+
|
|
7
|
+
jest.mock('@plone/volto/helpers', () => ({
|
|
8
|
+
addAppURL: jest.fn((url) => url),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
jest.mock('@plone/volto/registry', () => ({
|
|
12
|
+
__esModule: true,
|
|
13
|
+
default: {
|
|
14
|
+
settings: { prefixPath: '' },
|
|
15
|
+
},
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
jest.mock('./CaseStudyMap', () =>
|
|
19
|
+
jest.fn(() => <div id="case-study-map-mock">CaseStudyMapMock</div>),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
jest.mock('./CaseStudyFilters', () => ({
|
|
23
|
+
ActiveFilters: jest.fn(() => (
|
|
24
|
+
<div id="active-filters-mock">ActiveFiltersMock</div>
|
|
25
|
+
)),
|
|
26
|
+
CaseStudyFilters: jest.fn(() => (
|
|
27
|
+
<div id="case-study-fiters-mock">CaseStudyFiltersMock</div>
|
|
28
|
+
)),
|
|
29
|
+
SearchBox: jest.fn(() => <div id="search-box-mock">SearchBoxMock</div>),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
jest.mock('./utils', () => ({
|
|
33
|
+
filterCases: jest.fn(() => [{ id: 1, title: 'Filtered Case' }]),
|
|
34
|
+
getFilters: jest.fn(() => ['filter1', 'filter2']),
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
jest.mock('./hooks', () => ({
|
|
38
|
+
useCases: jest.fn(),
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
beforeAll(() => {
|
|
42
|
+
global.__SERVER__ = false;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('CaseStudyExplorerView', () => {
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
jest.clearAllMocks();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('renders filters and map when there are cases', () => {
|
|
51
|
+
useCases.mockReturnValue([{ id: 1, title: 'Test Case' }]);
|
|
52
|
+
|
|
53
|
+
const { container } = render(<CaseStudyExplorerView />);
|
|
54
|
+
|
|
55
|
+
expect(container.querySelector('#search-box-mock')).toBeInTheDocument();
|
|
56
|
+
expect(container.querySelector('#active-filters-mock')).toBeInTheDocument();
|
|
57
|
+
expect(
|
|
58
|
+
container.querySelector('#case-study-fiters-mock'),
|
|
59
|
+
).toBeInTheDocument();
|
|
60
|
+
expect(container.querySelector('#case-study-map-mock')).toBeInTheDocument();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('hides filters when caseStudiesIds is provided', () => {
|
|
64
|
+
useCases.mockReturnValue([{ id: 1, title: 'Test Case' }]);
|
|
65
|
+
|
|
66
|
+
const { container } = render(
|
|
67
|
+
<CaseStudyExplorerView caseStudiesIds={[1, 2]} />,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
expect(container.querySelector('#search-box-mock')).not.toBeInTheDocument();
|
|
71
|
+
expect(
|
|
72
|
+
container.querySelector('#active-filters-mock'),
|
|
73
|
+
).not.toBeInTheDocument();
|
|
74
|
+
expect(
|
|
75
|
+
container.querySelector('#case-study-fiters-mock'),
|
|
76
|
+
).not.toBeInTheDocument();
|
|
77
|
+
expect(container.querySelector('#case-study-map-mock')).toBeInTheDocument();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('does not render map when there are no cases', () => {
|
|
81
|
+
useCases.mockReturnValue([]);
|
|
82
|
+
|
|
83
|
+
const { container } = render(<CaseStudyExplorerView />);
|
|
84
|
+
|
|
85
|
+
expect(
|
|
86
|
+
container.querySelector('#case-study-map-mock'),
|
|
87
|
+
).not.toBeInTheDocument();
|
|
88
|
+
});
|
|
89
|
+
});
|