@eeacms/volto-n2k 1.1.2 → 1.1.4

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 CHANGED
@@ -4,6 +4,24 @@ 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.1.4](https://github.com/eea/volto-n2k/compare/1.1.3...1.1.4) - 16 January 2025
8
+
9
+ #### :bug: Bug Fixes
10
+
11
+ - fix: remove number for priority habitat type [Claudia Ifrim - [`7d65e7d`](https://github.com/eea/volto-n2k/commit/7d65e7d95de554b0ea3dc34e960f59fb9b9d88e7)]
12
+
13
+ ### [1.1.3](https://github.com/eea/volto-n2k/compare/1.1.2...1.1.3) - 9 January 2025
14
+
15
+ #### :house: Internal changes
16
+
17
+ - style: Automated code fix [eea-jenkins - [`5364109`](https://github.com/eea/volto-n2k/commit/536410947c52c860c6c51ddd73f3d829880cadbe)]
18
+ - style: Automated code fix [eea-jenkins - [`6c96546`](https://github.com/eea/volto-n2k/commit/6c965460c1d7cfd124981b68e8db64e204846a3d)]
19
+
20
+ #### :hammer_and_wrench: Others
21
+
22
+ - fix sonarqube errors [Claudia Ifrim - [`bbd9b7a`](https://github.com/eea/volto-n2k/commit/bbd9b7a0fb6acf40c3edbf5e2a034b7df0e2048b)]
23
+ - comment filters for habitats, fix z-index for filters on species list [Claudia Ifrim - [`44533aa`](https://github.com/eea/volto-n2k/commit/44533aa781b3b374320071b7cab0adbad322ad5b)]
24
+ - fix ImageText editor [Miu Razvan - [`67e6611`](https://github.com/eea/volto-n2k/commit/67e66113d289d5b3984909aee94e6b343fcd69a9)]
7
25
  ### [1.1.2](https://github.com/eea/volto-n2k/compare/1.1.1...1.1.2) - 11 December 2024
8
26
 
9
27
  #### :hammer_and_wrench: Others
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-n2k",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "description": "volto-n2k: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -27,8 +27,8 @@
27
27
  "@eeacms/volto-datablocks": "*",
28
28
  "@eeacms/volto-openlayers-map": "*",
29
29
  "@eeacms/volto-resize-helper": "*",
30
- "@eeacms/volto-tabs-block": "*",
31
30
  "@eeacms/volto-spotlight": "*",
31
+ "@eeacms/volto-tabs-block": "*",
32
32
  "d3": "^7.6.1",
33
33
  "d3-shape": "^3.1.0",
34
34
  "react-lazy-load-image-component": "1.5.1",
@@ -18,8 +18,7 @@ const Edit = (props) => {
18
18
  const { data = {}, selected = false } = props;
19
19
  const schema = getSchema();
20
20
  const value = { children: data.value || [], isVoid: Editor.isVoid };
21
- const valueUndefined =
22
- !value.children.length || Editor.string(value, []) === '';
21
+ const valueUndefined = !value.children.length;
23
22
 
24
23
  const handleKeyDown = (e, index, { disableEnter = false } = {}) => {
25
24
  if (e.key === 'Enter' && !e.shiftKey && !disableEnter) {
@@ -0,0 +1,49 @@
1
+ import React from 'react';
2
+ import { Container } from 'semantic-ui-react';
3
+ import cx from 'classnames';
4
+
5
+ const HabitatsGroups = (props) => {
6
+ const [habitatsGroups, setHabitatsGroups] = React.useState([]);
7
+ const {
8
+ provider_data = {},
9
+ activeHabitatsGroup = 'All',
10
+ setActiveHabitatsGroup = () => {},
11
+ } = props;
12
+
13
+ React.useEffect(() => {
14
+ setHabitatsGroups(
15
+ [
16
+ ...(provider_data.habitat_group?.length ? ['All'] : []),
17
+ ...new Set(provider_data.habitat_group || []),
18
+ ].sort((a, b) => {
19
+ if (a === 'All') return -1;
20
+ if (b === 'All') return 1;
21
+ return a.localeCompare(b);
22
+ }),
23
+ );
24
+ /* eslint-disable-next-line */
25
+ }, [JSON.stringify(provider_data)]);
26
+
27
+ return (
28
+ <div className="habitats-groups">
29
+ <Container>
30
+ {habitatsGroups.map((species) => (
31
+ <button
32
+ key={`group-filter-${species}`}
33
+ className={cx({
34
+ 'habitats-group': true,
35
+ active: activeHabitatsGroup === species,
36
+ })}
37
+ onClick={() => {
38
+ setActiveHabitatsGroup(species);
39
+ }}
40
+ >
41
+ {species}
42
+ </button>
43
+ ))}
44
+ </Container>
45
+ </div>
46
+ );
47
+ };
48
+
49
+ export default HabitatsGroups;
@@ -0,0 +1,52 @@
1
+ import React from 'react';
2
+ import { Dropdown } from 'semantic-ui-react';
3
+ import { Icon } from '@plone/volto/components';
4
+ import upSVG from '@plone/volto/icons/up.svg';
5
+ import downSVG from '@plone/volto/icons/down.svg';
6
+
7
+ const antonyms = {
8
+ ASC: 'DESC',
9
+ DESC: 'ASC',
10
+ };
11
+
12
+ const View = (props) => {
13
+ const { sortBy, setSortBy } = props;
14
+
15
+ const sortByOptions = [
16
+ { text: 'Name', value: 'scientific_name', key: 'scientific_name' },
17
+ ];
18
+
19
+ return (
20
+ <Dropdown aria-label="Sort habitats by" text="SORT BY" floating button>
21
+ <Dropdown.Menu>
22
+ {sortByOptions.map((option) => (
23
+ <Dropdown.Item
24
+ key={option.text}
25
+ active={sortBy === option.value}
26
+ onClick={(e, data) => {
27
+ const activePoperty = sortBy[0];
28
+ const activeOrder = sortBy[1];
29
+ const newOrder =
30
+ activePoperty === option.value ? antonyms[activeOrder] : 'ASC';
31
+ setSortBy([option.value, newOrder]);
32
+ }}
33
+ >
34
+ <p>
35
+ {option.text}
36
+ {sortBy[0] === option.value ? (
37
+ <Icon
38
+ name={sortBy[1] === 'ASC' ? upSVG : downSVG}
39
+ size="14px"
40
+ />
41
+ ) : (
42
+ ''
43
+ )}
44
+ </p>
45
+ </Dropdown.Item>
46
+ ))}
47
+ </Dropdown.Menu>
48
+ </Dropdown>
49
+ );
50
+ };
51
+
52
+ export default View;
@@ -0,0 +1,201 @@
1
+ import React from 'react';
2
+ import {
3
+ Container,
4
+ Dropdown,
5
+ Sidebar,
6
+ Checkbox,
7
+ Label,
8
+ } from 'semantic-ui-react';
9
+ import { Icon } from '@plone/volto/components';
10
+ // import filterSVG from '@plone/volto/icons/filter.svg';
11
+ import clearSVG from '@plone/volto/icons/clear.svg';
12
+
13
+ import HabitatsGroups from './HabitatsGroups';
14
+ import SortBy from './SortBy';
15
+
16
+ import { filtersLabels } from '../utils';
17
+
18
+ const SidebarFilter = (props) => {
19
+ const { activeFilters, filters, filter, index, setActiveFilters } = props;
20
+ return (
21
+ <div className="filter">
22
+ <div className="header">
23
+ <p className="title">
24
+ {filtersLabels[filter].getTitle().toUpperCase()}
25
+ {!index ? (
26
+ <button
27
+ className="reset-button"
28
+ onClick={() => {
29
+ setActiveFilters({});
30
+ }}
31
+ >
32
+ Reset
33
+ </button>
34
+ ) : (
35
+ ''
36
+ )}
37
+ </p>
38
+ </div>
39
+ {Object.keys(filters[filter]).map((field) => (
40
+ <div key={filtersLabels[filter][field]} className="checkbox-wrapper">
41
+ <Checkbox
42
+ name={filter}
43
+ label={filtersLabels[filter][field]}
44
+ checked={activeFilters[filter]?.includes(field) || false}
45
+ onClick={() => {
46
+ const newActiveFilters = { ...activeFilters };
47
+ if (newActiveFilters[filter]?.includes(field)) {
48
+ newActiveFilters[filter].splice(
49
+ newActiveFilters[filter].indexOf(field),
50
+ 1,
51
+ );
52
+ } else {
53
+ newActiveFilters[filter] = [
54
+ ...(activeFilters[filter] || []),
55
+ field,
56
+ ];
57
+ }
58
+ if (!newActiveFilters[filter].length) {
59
+ delete newActiveFilters[filter];
60
+ }
61
+ setActiveFilters(newActiveFilters);
62
+ }}
63
+ />
64
+ <p className="count">{filters[filter][field]}</p>
65
+ </div>
66
+ ))}
67
+ </div>
68
+ );
69
+ };
70
+
71
+ const View = (props) => {
72
+ const [visible, setVisible] = React.useState(false);
73
+ const [filters, setFilters] = React.useState({});
74
+ const {
75
+ provider_data,
76
+ activeFilters,
77
+ filteredHabitats,
78
+ pagination,
79
+ sortBy,
80
+ habitats,
81
+ setActiveFilters,
82
+ setPagination,
83
+ setSortBy,
84
+ } = props;
85
+ const itemsPerPageOptions = [
86
+ { key: '10', text: '10', value: 10 },
87
+ { key: '25', text: '25', value: 25 },
88
+ { key: '50', text: '50', value: 50 },
89
+ { key: '100', text: '100', value: 100 },
90
+ ];
91
+
92
+ const updateFilters = () => {
93
+ const newFilters = {};
94
+ Object.keys(filtersLabels).forEach((filter) => {
95
+ newFilters[filter] = {};
96
+ for (let key in filtersLabels[filter]) {
97
+ if (key !== 'getTitle') {
98
+ newFilters[filter][key] =
99
+ filteredHabitats.filter((habitats) => {
100
+ return !!habitats.filter((habitat) => habitat[filter] === key)
101
+ .length;
102
+ }).length || 'none';
103
+ }
104
+ }
105
+ });
106
+ setFilters(newFilters);
107
+ };
108
+
109
+ React.useEffect(() => {
110
+ if (provider_data && habitats.length) {
111
+ updateFilters();
112
+ }
113
+ /* eslint-disable-next-line */
114
+ }, [filteredHabitats]);
115
+
116
+ return (
117
+ <div className="habitats-filters">
118
+ <HabitatsGroups {...props} />
119
+ <Container>
120
+ <div className="active-filters">
121
+ {Object.keys(activeFilters).map((filter) =>
122
+ activeFilters[filter].map((field) => (
123
+ <Label
124
+ className="active-filter"
125
+ key={`active-filter-${field}`}
126
+ as="span"
127
+ >
128
+ {filtersLabels[filter][field]}
129
+ <Icon
130
+ name={clearSVG}
131
+ size="18px"
132
+ onClick={() => {
133
+ const newActiveFilters = { ...activeFilters };
134
+ if (newActiveFilters[filter]?.includes(field)) {
135
+ newActiveFilters[filter].splice(
136
+ newActiveFilters[filter].indexOf(field),
137
+ 1,
138
+ );
139
+ }
140
+ if (!newActiveFilters[filter].length) {
141
+ delete newActiveFilters[filter];
142
+ }
143
+ setActiveFilters(newActiveFilters);
144
+ }}
145
+ />
146
+ </Label>
147
+ )),
148
+ )}
149
+ </div>
150
+ <div className="toolbar">
151
+ <SortBy sortBy={sortBy} setSortBy={setSortBy} />
152
+ <Dropdown
153
+ aria-label="Set number of habitats per page"
154
+ placeholder="Items per page"
155
+ value={pagination.itemsPerPage}
156
+ floating
157
+ button
158
+ options={itemsPerPageOptions}
159
+ onChange={(e, data) => {
160
+ setPagination({ ...pagination, itemsPerPage: data.value });
161
+ }}
162
+ />
163
+ {/* <button aria-label="Set filters" onClick={() => setVisible(!visible)}>
164
+ <Icon name={filterSVG} size="24px" />
165
+ </button> */}
166
+ </div>
167
+ </Container>
168
+ <Sidebar
169
+ as="div"
170
+ className="z-full"
171
+ animation="overlay"
172
+ direction="right"
173
+ onHide={() => setVisible(false)}
174
+ vertical
175
+ visible={visible}
176
+ width="wide"
177
+ >
178
+ <div className="sidebar-header">
179
+ <p className="title">Filters</p>
180
+ <button className="clear-button" onClick={() => setVisible(false)}>
181
+ <Icon name={clearSVG} size="20px" color="#fff" />
182
+ </button>
183
+ </div>
184
+ <div className="filters">
185
+ {Object.keys(filters).map((filter, index) => (
186
+ <SidebarFilter
187
+ key={filter}
188
+ index={index}
189
+ activeFilters={activeFilters}
190
+ filters={filters}
191
+ filter={filter}
192
+ setActiveFilters={setActiveFilters}
193
+ />
194
+ ))}
195
+ </div>
196
+ </Sidebar>
197
+ </div>
198
+ );
199
+ };
200
+
201
+ export default View;
@@ -0,0 +1,4 @@
1
+ import HabitatsGroupsFilters from './HabitatsGroups';
2
+ import Filters from './View';
3
+
4
+ export { HabitatsGroupsFilters, Filters };
@@ -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`
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
  };