@eeacms/volto-n2k 1.1.1 → 1.1.3

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.
Files changed (23) hide show
  1. package/CHANGELOG.md +19 -2
  2. package/package.json +1 -1
  3. package/src/components/manage/Blocks/ExploreHabitats/View.jsx +71 -66
  4. package/src/components/manage/Blocks/HabitatsBanner/View.jsx +4 -1
  5. package/src/components/manage/Blocks/ImageText/Edit.jsx +1 -2
  6. package/src/components/manage/Blocks/SiteHabitatsList/Filters/HabitatsGroups.jsx +49 -0
  7. package/src/components/manage/Blocks/SiteHabitatsList/Filters/SortBy.jsx +52 -0
  8. package/src/components/manage/Blocks/SiteHabitatsList/Filters/View.jsx +201 -0
  9. package/src/components/manage/Blocks/SiteHabitatsList/Filters/index.js +4 -0
  10. package/src/components/manage/Blocks/SiteHabitatsList/View.jsx +214 -84
  11. package/src/components/manage/Blocks/SiteHabitatsList/style.less +256 -45
  12. package/src/components/manage/Blocks/SiteHabitatsList/utils.js +33 -0
  13. package/src/components/manage/Blocks/SiteHabitatsListOld/View.jsx +106 -0
  14. package/src/components/manage/Blocks/SiteHabitatsListOld/index.js +17 -0
  15. package/src/components/manage/Blocks/SiteHabitatsListOld/schema.js +19 -0
  16. package/src/components/manage/Blocks/SiteHabitatsListOld/style.less +70 -0
  17. package/src/components/manage/Blocks/SiteProtectedHabitats/View.jsx +11 -9
  18. package/src/components/manage/Blocks/SiteProtectedSpecies/View.jsx +11 -9
  19. package/src/components/manage/Blocks/SiteSpeciesList/Filters/SortBy.jsx +1 -1
  20. package/src/components/manage/Blocks/SiteSpeciesList/Filters/SpeciesGroups.jsx +10 -4
  21. package/src/components/manage/Blocks/SiteSpeciesList/Filters/View.jsx +1 -0
  22. package/src/components/manage/Blocks/SiteSpeciesList/style.less +30 -22
  23. package/src/components/manage/Blocks/SpeciesBanner/View.jsx +1 -0
@@ -1,104 +1,234 @@
1
1
  import React from 'react';
2
- import findIndex from 'lodash/findIndex';
3
- import { Icon } from '@plone/volto/components';
4
2
  import { Link } from 'react-router-dom';
5
- import cx from 'classnames';
6
- import { getObjectByIndex } from '@eeacms/volto-n2k/helpers';
7
- import downKeySVG from '@plone/volto/icons/down-key.svg';
8
- import upKeySVG from '@plone/volto/icons/up-key.svg';
3
+ import { Container, Pagination, Grid } from 'semantic-ui-react';
4
+ import { getObjectByIndex, photoPlaceholders } from '@eeacms/volto-n2k/helpers';
5
+ import { Filters } from './Filters';
6
+ // import { getPopulationString, getLabelString } from './utils';
7
+
9
8
  import './style.less';
10
9
 
10
+ const getCurrentPageLength = (pagination, arr) => {
11
+ const totalPages = Math.ceil(pagination.totalItems / pagination.itemsPerPage);
12
+ if (arr.length < pagination.itemsPerPage) return arr.length;
13
+ if (totalPages === pagination.activePage) {
14
+ return arr.length - (pagination.activePage - 1) * pagination.itemsPerPage;
15
+ }
16
+ return pagination.itemsPerPage;
17
+ };
18
+
11
19
  const View = (props) => {
12
- const [habitats, setHabitats] = React.useState({});
13
- const [expandedHabitats, setExpandedHabitats] = React.useState([]);
14
- const { provider_data = {}, placeholder = 'No results' } = props;
20
+ const {
21
+ provider_data = {},
22
+ placeholder = 'This site does not host any protected habitats',
23
+ } = props;
24
+ const dataReady = React.useRef(false);
25
+ const [activeHabitatsGroup, setActiveHabitatsGroup] = React.useState('All');
26
+ const [filters, setFilters] = React.useState({});
27
+ const [habitats, spetHabitats] = React.useState([]);
28
+ const [filteredHabitats, setFilteredHabitats] = React.useState([]);
29
+ const [pagination, setPagination] = React.useState({
30
+ activePage: 1,
31
+ totalItems: 0,
32
+ itemsPerPage: 10,
33
+ });
34
+ const [sortBy, setSortBy] = React.useState(['scientific_name', 'ASC']);
35
+
36
+ const getSortedHabitats = (habitats = []) => {
37
+ const property = sortBy[0];
38
+ const order = sortBy[1];
39
+
40
+ return habitats.sort((a, b) => {
41
+ const a_value = a[0][property];
42
+ const b_value = b[0][property];
43
+ if (a_value < b_value) return order === 'ASC' ? -1 : 1;
44
+ if (a_value > b_value) return order === 'ASC' ? 1 : -1;
45
+ return 0;
46
+ });
47
+ };
48
+
49
+ const getfilteredHabitats = (habitats = []) => {
50
+ const activeFilters = {
51
+ ...filters,
52
+ ...(activeHabitatsGroup !== 'All'
53
+ ? { habitat_group: [activeHabitatsGroup] }
54
+ : {}),
55
+ };
56
+
57
+ const filteredHabitats = habitats.filter((items, index) => {
58
+ let itemsHaveFilter = true;
59
+ Object.keys(activeFilters).forEach((filter) => {
60
+ let habitatHasFilter = false;
61
+ items.forEach((item) => {
62
+ if (activeFilters[filter].includes(item[filter])) {
63
+ habitatHasFilter = true;
64
+ }
65
+ });
66
+ if (!habitatHasFilter) {
67
+ itemsHaveFilter = false;
68
+ }
69
+ });
70
+ return itemsHaveFilter;
71
+ });
72
+
73
+ setPagination({
74
+ ...pagination,
75
+ activePage: 1,
76
+ totalItems: filteredHabitats.length,
77
+ });
78
+
79
+ return filteredHabitats;
80
+ };
15
81
 
16
82
  React.useEffect(() => {
17
- const newHabitats = {};
18
- if (provider_data?.habitat_group?.length) {
19
- provider_data.habitat_group.forEach((habitat, index) => {
20
- if (!newHabitats[habitat]) {
21
- newHabitats[habitat] = [];
83
+ dataReady.current = false;
84
+ const habitatsIndex = {};
85
+ const newHabitats = [];
86
+ if (provider_data.scientific_name?.length) {
87
+ provider_data.scientific_name.forEach((_, index) => {
88
+ const habitat = getObjectByIndex(provider_data, index);
89
+ if (!(habitatsIndex[habitat.code_2000] >= 0)) {
90
+ habitatsIndex[habitat.code_2000] = index;
91
+ }
92
+ if (!newHabitats[habitatsIndex[habitat.code_2000]]) {
93
+ newHabitats[habitatsIndex[habitat.code_2000]] = [];
22
94
  }
23
- newHabitats[habitat].push(getObjectByIndex(provider_data, index));
95
+ newHabitats[habitatsIndex[habitat.code_2000]].push(habitat);
24
96
  });
25
97
  }
26
- setHabitats(newHabitats);
98
+ spetHabitats(getSortedHabitats(newHabitats.filter((habitats) => habitats)));
27
99
  /* eslint-disable-next-line */
28
100
  }, [JSON.stringify(provider_data)]);
29
101
 
102
+ React.useEffect(() => {
103
+ const filteredHabitats = getSortedHabitats(
104
+ getfilteredHabitats([...habitats]),
105
+ );
106
+ setFilteredHabitats(filteredHabitats);
107
+ /* eslint-disable-next-line */
108
+ }, [habitats, sortBy, activeHabitatsGroup, filters]);
109
+
30
110
  return (
31
- <div className="site-habitats-list">
32
- {Object.keys(habitats)?.length ? (
33
- Object.keys(habitats)
34
- .sort((a, b) => a.localeCompare(b))
35
- .map((habitat) => {
36
- const expanded = expandedHabitats.includes(habitat);
37
- return (
38
- <div className="habitat" key={habitat}>
39
- <div
40
- className={cx({
41
- 'habitat-toolbar': true,
42
- marginless: !expanded,
43
- })}
44
- >
45
- <div className="habitat-name">
46
- <h3>{habitat}</h3>
47
- <p className="count">{habitats[habitat].length}</p>
48
- </div>
49
- <Icon
50
- name={expanded ? upKeySVG : downKeySVG}
51
- onClick={(e) => {
52
- const index = findIndex(
53
- expandedHabitats,
54
- (name) => name === habitat,
55
- );
56
- if (index === -1) {
57
- setExpandedHabitats((prevExpandedHabitats) => [
58
- ...prevExpandedHabitats,
59
- habitat,
60
- ]);
61
- } else {
62
- setExpandedHabitats((prevExpandedHabitats) => {
63
- const newExpandedHabitats = [...prevExpandedHabitats];
64
- newExpandedHabitats.splice(index, 1);
65
- return newExpandedHabitats;
66
- });
67
- }
68
- e.preventDefault();
69
- e.stopPropagation();
70
- }}
71
- color="#8C8C8C"
72
- size="32px"
73
- />
74
- </div>
111
+ <div className="site-habitats-list full-width">
112
+ {props.mode !== 'edit' && habitats.length ? (
113
+ <Filters
114
+ {...props}
115
+ activeHabitatsGroup={activeHabitatsGroup}
116
+ activeFilters={filters}
117
+ filteredHabitats={filteredHabitats}
118
+ pagination={pagination}
119
+ sortBy={sortBy}
120
+ habitats={habitats}
121
+ setActiveHabitatsGroup={setActiveHabitatsGroup}
122
+ setActiveFilters={setFilters}
123
+ setPagination={setPagination}
124
+ setSortBy={setSortBy}
125
+ />
126
+ ) : (
127
+ ''
128
+ )}
75
129
 
76
- {expanded
77
- ? habitats[habitat].map((item, index) => (
78
- <div
79
- className="habitat-item"
80
- key={`${habitat}-${index}-item`}
81
- >
82
- <Link
83
- className="description"
84
- to={`/habitats/${item.code_2000}`}
85
- >
86
- {item.habitat_description} ({item.code_2000})
87
- </Link>
88
- <p className="coverage">
89
- {item.coverage_ha.toFixed(2)} ha (
90
- {(item.coverage_ha / 100).toFixed(4)} km
91
- <sup>2</sup>)
130
+ <div className="habitats-list">
131
+ <Container>
132
+ {filteredHabitats.length ? (
133
+ Array.from(
134
+ {
135
+ length: getCurrentPageLength(pagination, filteredHabitats),
136
+ },
137
+ (v, k) =>
138
+ k + (pagination.activePage - 1) * pagination.itemsPerPage,
139
+ ).map((index) => {
140
+ const habitatsData = filteredHabitats[index][0];
141
+
142
+ return (
143
+ <Grid
144
+ className="habitats"
145
+ key={`${index}-${habitatsData.code_2000}`}
146
+ columns="12"
147
+ >
148
+ <Grid.Row>
149
+ <Grid.Column
150
+ mobile={12}
151
+ tablet={3}
152
+ computer={2}
153
+ className="habitats-photo"
154
+ >
155
+ <img
156
+ src={
157
+ habitatsData.picture_url ||
158
+ photoPlaceholders[habitatsData.habitat_group] ||
159
+ photoPlaceholders.Birds
160
+ }
161
+ alt={habitatsData.habitat_group}
162
+ />
163
+ </Grid.Column>
164
+ <Grid.Column
165
+ mobile={12}
166
+ tablet={9}
167
+ computer={10}
168
+ className="habitats-details"
169
+ >
170
+ <div className="metadata">
171
+ <div className="name">
172
+ <Link
173
+ as="h3"
174
+ to={`/habitats/${habitatsData.code_2000}`}
175
+ >
176
+ {habitatsData.scientific_name
177
+ ? habitatsData.scientific_name + ' '
178
+ : ''}
179
+ <span className="code-2000">
180
+ {' '}
181
+ - {habitatsData.code_2000 || 'NA'}
182
+ </span>
183
+ </Link>
184
+ </div>
185
+ {filteredHabitats[index].map((habitat, index) => (
186
+ <p
187
+ className="habitat-data blue"
188
+ style={{ fontSize: '1.2rem' }}
189
+ key={`habitat-${index}-${habitat.code_2000}`}
190
+ >
191
+ Cover: {habitat.coverage_ha.toFixed(2)} ha (
192
+ {(habitat.coverage_ha / 100).toFixed(4)} km²)
193
+ {habitat.habitat_prioriy
194
+ ? `; Priority habitat type: ${habitat.habitat_prioriy}`
195
+ : ''}
196
+ </p>
197
+ ))}
198
+ </div>
199
+ <div className="footer-metadata">
200
+ <p className="green">
201
+ Appears in {habitatsData.number_sites} sites
92
202
  </p>
93
203
  </div>
94
- ))
95
- : ''}
96
- </div>
97
- );
98
- })
99
- ) : (
100
- <p>{placeholder}</p>
101
- )}
204
+ </Grid.Column>
205
+ </Grid.Row>
206
+ </Grid>
207
+ );
208
+ })
209
+ ) : habitats.length ? (
210
+ <div className="empty">No results</div>
211
+ ) : (
212
+ ''
213
+ )}
214
+ {!habitats?.length ? <div className="empty">{placeholder}</div> : ''}
215
+ {pagination.totalItems > 0 ? (
216
+ <Pagination
217
+ activePage={pagination.activePage}
218
+ totalPages={Math.ceil(
219
+ pagination.totalItems / pagination.itemsPerPage,
220
+ )}
221
+ onPageChange={(e, data) => {
222
+ setPagination({ ...pagination, activePage: data.activePage });
223
+ }}
224
+ prevItem={null}
225
+ nextItem={null}
226
+ />
227
+ ) : (
228
+ ''
229
+ )}
230
+ </Container>
231
+ </div>
102
232
  </div>
103
233
  );
104
234
  };
@@ -1,70 +1,281 @@
1
- .site-habitats-list {
2
- .icon {
3
- cursor: pointer;
4
- }
1
+ div#view .site-habitats-list .habitats-group-filter .ui.container > *,
2
+ div#view .site-habitats-list .habitats-filters .ui.container > *,
3
+ div#view .site-habitats-list .habitats-list .ui.container > * {
4
+ margin-bottom: 0;
5
+ }
5
6
 
6
- .habitat {
7
- padding: 1rem 2rem;
8
- border: 1px solid #e8e8e8;
9
- border-radius: 10px;
10
- margin-bottom: 1rem;
11
- background-color: #fff !important;
7
+ .site-habitats-list {
8
+ margin-top: -1em;
12
9
 
13
- .habitat-toolbar {
10
+ .habitats-filters {
11
+ .ui.container {
14
12
  display: flex;
15
- align-items: center;
16
13
  justify-content: space-between;
14
+
15
+ .active-filters {
16
+ .ui.label.active-filter {
17
+ display: inline-flex;
18
+ flex-flow: row;
19
+ align-items: center;
20
+ border: 1px solid #c5c5c5;
21
+ border-radius: 18px;
22
+ color: #000;
23
+ font-weight: 400;
24
+
25
+ svg.icon {
26
+ margin-right: 0;
27
+ margin-left: 0.5rem;
28
+ cursor: pointer;
29
+ }
30
+ }
31
+ }
32
+
33
+ .toolbar {
34
+ display: flex;
35
+ flex-flow: row;
36
+ justify-content: center;
37
+
38
+ .ui.button.dropdown,
39
+ button {
40
+ border: 1px solid #0000001f;
41
+ margin-bottom: 0;
42
+ background-color: #fff !important;
43
+ color: #000;
44
+ cursor: pointer;
45
+ }
46
+ }
47
+ }
48
+
49
+ .habitats-groups {
17
50
  margin-bottom: 1rem;
51
+ background-color: #00a390;
52
+ text-align: center;
53
+ white-space: nowrap;
54
+
55
+ button.habitats-group {
56
+ padding: 1rem;
57
+ border: none;
58
+ background-color: transparent;
59
+ color: #fff !important;
60
+ cursor: pointer;
61
+ font-size: 16px;
62
+ text-transform: uppercase;
63
+
64
+ &::after {
65
+ display: block;
66
+ width: calc(100% + 8px);
67
+ height: 3px;
68
+ margin-top: 2px;
69
+ background: transparent;
70
+ content: '';
71
+ transform: translateX(-4px);
72
+ }
18
73
 
19
- &.marginless {
20
- margin-bottom: 0;
74
+ &.active,
75
+ &:hover {
76
+ &::after {
77
+ background: #ffffff;
78
+ }
79
+ }
80
+ }
81
+
82
+ .ui.container {
83
+ display: block;
84
+ overflow-x: auto;
85
+ text-align: center;
21
86
  }
22
87
  }
23
88
 
24
- .habitat-name {
25
- display: flex;
26
- align-items: center;
27
-
28
- h3 {
29
- margin-right: 1rem;
30
- margin-bottom: 0;
31
- color: #013c60;
32
- font-weight: 400;
33
- line-height: 25px;
89
+ .ui.sidebar {
90
+ background-color: #fff !important;
91
+
92
+ &.right.visible {
93
+ z-index: 800;
94
+ box-shadow: 0px 0px 13px -5px #000000;
34
95
  }
35
96
 
36
- .count {
37
- width: 24px;
38
- height: 24px;
39
- border-radius: 6px;
40
- margin-bottom: 0;
97
+ .sidebar-header {
98
+ display: flex;
99
+ flex-flow: row;
100
+ align-items: center;
101
+ justify-content: space-between;
102
+ padding: 2rem 1rem;
41
103
  background-color: #00a390;
42
- color: #fff !important;
43
- font-weight: bold;
44
- line-height: 24px;
45
- text-align: center;
104
+
105
+ .title {
106
+ margin-bottom: 0;
107
+ color: #fff !important;
108
+ font-size: 20px;
109
+ font-weight: bold;
110
+ }
111
+
112
+ .clear-button {
113
+ border: none;
114
+ background-color: transparent;
115
+ cursor: pointer;
116
+ }
117
+ }
118
+
119
+ .filters {
120
+ padding: 2rem 0;
121
+
122
+ .filter {
123
+ padding: 0 1rem 1rem 1rem;
124
+ border-bottom: 2px solid #e0e0e0;
125
+ margin-bottom: 2rem;
126
+
127
+ .header {
128
+ padding: 0;
129
+ margin-bottom: 2rem;
130
+ background-color: transparent;
131
+
132
+ .title {
133
+ color: #000;
134
+ font-weight: 600;
135
+
136
+ .reset-button {
137
+ border: none;
138
+ margin-left: 1rem;
139
+ background-color: transparent;
140
+ color: #00a390;
141
+ cursor: pointer;
142
+
143
+ &:hover {
144
+ opacity: 0.8;
145
+ }
146
+ }
147
+ }
148
+ }
149
+
150
+ .checkbox-wrapper {
151
+ display: flex;
152
+ align-items: center;
153
+ justify-content: space-between;
154
+ margin-bottom: 1rem;
155
+
156
+ .count {
157
+ color: #636363;
158
+ }
159
+ }
160
+ }
46
161
  }
47
162
  }
163
+ }
48
164
 
49
- .habitat-item {
50
- .description {
51
- color: #013c60;
52
- font-size: 1.2rem;
53
- font-weight: 400;
165
+ .habitats-list {
166
+ .ui.grid.habitats {
167
+ margin-top: 0;
168
+ margin-right: 0;
169
+ margin-bottom: 0;
54
170
 
55
- &:hover {
56
- opacity: 0.9;
171
+ .row {
172
+ padding: 1rem 0;
173
+ }
174
+
175
+ .habitats-photo {
176
+ overflow: hidden;
177
+ padding-right: 0;
178
+
179
+ img {
180
+ width: 100%;
181
+ height: 100%;
182
+ border-bottom-left-radius: 10px;
183
+ border-top-left-radius: 10px;
184
+ object-fit: cover;
185
+
186
+ @media only screen and (max-width: 767px) {
187
+ border-bottom-left-radius: 0;
188
+ border-top-left-radius: 10px;
189
+ border-top-right-radius: 10px;
190
+ }
57
191
  }
58
192
  }
59
193
 
60
- .coverage {
61
- color: #8c8c8c;
194
+ .habitats-details {
195
+ display: flex;
196
+ flex-flow: column;
197
+ justify-content: space-between;
198
+ padding: 0;
199
+ background-color: #fff !important;
200
+ border-bottom-right-radius: 10px;
201
+ border-top-right-radius: 10px;
202
+
203
+ @media only screen and (max-width: 767px) {
204
+ margin-left: 0.625rem;
205
+ border-bottom-left-radius: 10px;
206
+ border-bottom-right-radius: 10;
207
+ border-top-left-radius: 0;
208
+ border-top-right-radius: 0;
209
+ }
210
+
211
+ .metadata {
212
+ padding: 0 2rem;
213
+ padding-top: 1rem;
214
+ margin-bottom: 1rem;
215
+
216
+ .name {
217
+ margin-bottom: 1rem;
218
+
219
+ a {
220
+ color: #013c60;
221
+ font-size: 25px;
222
+ font-weight: 400;
223
+ }
224
+
225
+ .code-2000 {
226
+ color: #0f8f22;
227
+ }
228
+ }
229
+
230
+ .habitat-data:not(:last-child) {
231
+ margin-bottom: 0.5rem;
232
+ }
233
+ }
234
+
235
+ .footer-metadata {
236
+ display: flex;
237
+ padding: 1rem 2rem;
238
+ border-top: 1px solid #e8e8e8;
239
+
240
+ p:not(.last-child) {
241
+ margin-right: 1rem;
242
+ }
243
+ }
244
+
245
+ .metadata,
246
+ .footer-metadata {
247
+ .blue {
248
+ color: #013c60;
249
+ }
250
+
251
+ .orange {
252
+ color: #ff9300;
253
+ }
254
+
255
+ .green {
256
+ color: #0f8f22;
257
+ }
258
+ }
62
259
  }
260
+ }
261
+
262
+ .ui.pagination.menu {
263
+ width: 100%;
264
+ justify-content: center;
265
+ background-color: transparent;
63
266
 
64
- p {
65
- margin-bottom: 0;
267
+ .item {
268
+ &.active {
269
+ color: #013c60;
270
+ font-weight: bold;
271
+ }
66
272
  }
67
- margin-bottom: 1rem;
273
+ }
274
+
275
+ .empty {
276
+ margin-top: 3rem;
277
+ margin-bottom: 3rem !important;
278
+ text-align: left;
68
279
  }
69
280
  }
70
281
  }
@@ -0,0 +1,33 @@
1
+ export const filtersLabels = {
2
+ population_type: {
3
+ p: 'Permanent',
4
+ r: 'Reproducing',
5
+ c: 'Concentration',
6
+ w: 'Wintering',
7
+ getTitle: () => 'Population type',
8
+ },
9
+ counting_unit: {
10
+ i: 'Individuals',
11
+ p: 'Pairs',
12
+ getTitle: () => 'Unit',
13
+ },
14
+ abundance_category: {
15
+ C: 'Common',
16
+ R: 'Rare',
17
+ V: 'Very rare',
18
+ P: 'Present',
19
+ getTitle: () => 'Abundance',
20
+ },
21
+ };
22
+
23
+ export const getPopulationString = (min, max, prefix = '') => {
24
+ return `${prefix}Population: min: ${min || 'not reported'}, max: ${
25
+ max || 'not reported'
26
+ }`;
27
+ };
28
+
29
+ export const getLabelString = (label, key, prefix = '') => {
30
+ return `${prefix}${filtersLabels[label].getTitle()}: ${
31
+ filtersLabels[label]?.[key]?.toLowerCase() || key || 'not reported'
32
+ }`;
33
+ };