@eeacms/volto-clms-theme 1.1.208 → 1.1.210

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.210](https://github.com/eea/volto-clms-theme/compare/1.1.209...1.1.210) - 10 February 2025
8
+
9
+ #### :hammer_and_wrench: Others
10
+
11
+ - CLMS-279774 (feat): Improved calendar selection [Urkorue - [`ab4c043`](https://github.com/eea/volto-clms-theme/commit/ab4c043e4a81c90bdcbdbdd0bf707da17037c34e)]
12
+ ### [1.1.209](https://github.com/eea/volto-clms-theme/compare/1.1.208...1.1.209) - 30 January 2025
13
+
14
+ #### :rocket: New Features
15
+
16
+ - feat: Improve the order of documents in the technical library of the product/product group - refs #283044 [ana-oprea - [`6feba8a`](https://github.com/eea/volto-clms-theme/commit/6feba8a82ec84a6186cee3da719de7b10a356947)]
17
+
18
+ #### :bug: Bug Fixes
19
+
20
+ - fix(test): modify widget tests for dataset and add test for product content type [ana-oprea - [`d7534cc`](https://github.com/eea/volto-clms-theme/commit/d7534cc4f26e77bcfcc91d3988f4f974ec3116f4)]
21
+
22
+ #### :hammer_and_wrench: Others
23
+
24
+ - fix eslint [ana-oprea - [`785ccf8`](https://github.com/eea/volto-clms-theme/commit/785ccf86f60e3275b7f392fb3b47f0eb6845f7b2)]
7
25
  ### [1.1.208](https://github.com/eea/volto-clms-theme/compare/1.1.207...1.1.208) - 13 January 2025
8
26
 
9
27
  #### :hammer_and_wrench: Others
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-clms-theme",
3
- "version": "1.1.208",
3
+ "version": "1.1.210",
4
4
  "description": "volto-clms-theme: Volto theme for CLMS site",
5
5
  "main": "src/index.js",
6
6
  "author": "CodeSyntax for the European Environment Agency",
@@ -27,7 +27,7 @@ const CclRelatedListingEdit = (props) => {
27
27
  (variation) => variation.id === data.variation,
28
28
  );
29
29
  TemplateView = theVariation?.template;
30
- template_id = theVariation.templateId;
30
+ template_id = theVariation.templateID;
31
31
  } else {
32
32
  TemplateView = defaultVariation.template;
33
33
  template_id = defaultVariation.id;
@@ -79,7 +79,7 @@ export const TimeseriesPicker = (props) => {
79
79
  <DatePicker
80
80
  id="start_date"
81
81
  inline
82
- selectsRange
82
+ selectsRange={download_limit_temporal_extent < 360 ? true : false}
83
83
  className="datepicker"
84
84
  minDate={new Date(start)}
85
85
  maxDate={new Date(end)}
@@ -87,14 +87,38 @@ export const TimeseriesPicker = (props) => {
87
87
  endDate={item?.TemporalFilter?.EndDate}
88
88
  // selectsStart
89
89
  onChange={(e) => {
90
- item.TemporalFilter = {
91
- StartDate: e[0],
92
- EndDate: e[1],
93
- };
94
- setStartValue(e[0]);
95
- setEndValue(e[1]);
90
+ if (download_limit_temporal_extent < 360) {
91
+ if (download_limit_temporal_extent > 180 && e[1]) {
92
+ e[1].setMonth(e[1].getMonth() + 1);
93
+ e[1].setDate(e[1].getDate() - 1);
94
+ }
95
+ item.TemporalFilter = {
96
+ StartDate: e[0],
97
+ EndDate: e[1],
98
+ };
99
+ setStartValue(e[0]);
100
+ setEndValue(e[1]);
101
+ } else {
102
+ let e2 = new Date(e);
103
+ e2.setDate(e2.getDate() + 364);
104
+ item.TemporalFilter = {
105
+ StartDate: e,
106
+ EndDate: e2,
107
+ };
108
+ setStartValue(e);
109
+ setEndValue(e2);
110
+ }
96
111
  }}
97
112
  dateFormat="dd.MM.yyyy"
113
+ showMonthYearPicker={
114
+ download_limit_temporal_extent > 180 &&
115
+ download_limit_temporal_extent < 360
116
+ ? true
117
+ : false
118
+ }
119
+ showYearPicker={
120
+ download_limit_temporal_extent >= 360 ? true : false
121
+ }
98
122
  calendarStartDay={1}
99
123
  popperPlacement="top"
100
124
  dropdownMode="select"
@@ -114,21 +138,52 @@ export const TimeseriesPicker = (props) => {
114
138
  </span>
115
139
  )}
116
140
  <br />
117
- Click the start and end dates, and then apply.
141
+ {download_limit_temporal_extent < 180 && (
142
+ <span>Click the start and end dates, and then apply.</span>
143
+ )}
144
+ {download_limit_temporal_extent > 180 &&
145
+ download_limit_temporal_extent < 360 && (
146
+ <span>Click the start and end months, and then apply.</span>
147
+ )}
148
+ {download_limit_temporal_extent > 360 && (
149
+ <span>Click the year, and then apply.</span>
150
+ )}
118
151
  {(!startValue ||
119
152
  !endValue ||
120
153
  !isValidDateRange({
121
154
  start: startValue,
122
155
  end: endValue,
123
156
  limit: download_limit_temporal_extent,
124
- })) && (
125
- <span>
126
- {' '}
127
- Allowed time range of {
128
- download_limit_temporal_extent
129
- } days.{' '}
130
- </span>
131
- )}
157
+ })) &&
158
+ download_limit_temporal_extent < 180 && (
159
+ <span>
160
+ {' '}
161
+ Allowed time range of {
162
+ download_limit_temporal_extent
163
+ } days.{' '}
164
+ </span>
165
+ )}
166
+ {(!startValue ||
167
+ !endValue ||
168
+ !isValidDateRange({
169
+ start: startValue,
170
+ end: endValue,
171
+ limit: download_limit_temporal_extent,
172
+ })) &&
173
+ download_limit_temporal_extent > 180 &&
174
+ download_limit_temporal_extent < 360 && (
175
+ <span> Allowed time range of 6 months. </span>
176
+ )}
177
+ {(!startValue ||
178
+ !endValue ||
179
+ !isValidDateRange({
180
+ start: startValue,
181
+ end: endValue,
182
+ limit: download_limit_temporal_extent,
183
+ })) &&
184
+ download_limit_temporal_extent > 360 && (
185
+ <span> Allowed time range of 1 year. </span>
186
+ )}
132
187
  <CclButton
133
188
  isButton={true}
134
189
  mode={'filled'}
@@ -121,3 +121,50 @@ div.mb-2 {
121
121
  margin-bottom: 1rem !important;
122
122
  margin-inline: 2rem !important;
123
123
  }
124
+
125
+ // .react-datepicker__month{
126
+ // display: none;
127
+ // }
128
+
129
+ // .react-datepicker__day-names{
130
+ // display: none;
131
+ // }
132
+
133
+ // .react-datepicker__month-container{
134
+ // width: 100%;
135
+ // }
136
+
137
+ .react-datepicker__month-text--in-range {
138
+ background-color: #7c8921 !important;
139
+ }
140
+
141
+ .react-datepicker__month-text--in-selecting-range {
142
+ background-color: #7c8921 !important;
143
+ }
144
+
145
+ .react-datepicker__month-text--range-start {
146
+ background-color: rgba(160, 177, 40, 0.2) !important;
147
+ color: black;
148
+ }
149
+
150
+ .react-datepicker__month-text--range-end {
151
+ background-color: rgba(160, 177, 40, 0.2) !important;
152
+ color: black;
153
+ }
154
+
155
+ .react-datepicker__year-text--in-range {
156
+ background-color: #7c8921 !important;
157
+ }
158
+
159
+ .react-datepicker-year-header {
160
+ width: 100%;
161
+ color: white;
162
+ }
163
+
164
+ .react-datepicker__year-wrapper {
165
+ max-width: 260px;
166
+ }
167
+
168
+ .react-datepicker__month-container {
169
+ width: 100%;
170
+ }
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useCallback, useMemo } from 'react';
1
+ import React, { useEffect, useCallback, useMemo, useState } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { useLocation } from 'react-router-dom';
4
4
  import { useDispatch, useSelector } from 'react-redux';
@@ -41,34 +41,39 @@ const DocumentItem = React.memo(({ item, provided }) => (
41
41
  const OrderDocumentsWidget = (props) => {
42
42
  const { id, formData, onChange, value } = props;
43
43
  const UID = formData?.UID;
44
+ const contentType = formData?.['@type'] || undefined;
44
45
  const location = useLocation();
45
46
  const dispatch = useDispatch();
46
47
  const searchSubrequests = useSelector((state) => state.search.subrequests);
47
- const [documentsList, setDocumentsList] = React.useState([]);
48
- const [isLoading, setIsLoading] = React.useState(true);
48
+ const [documentsList, setDocumentsList] = useState([]);
49
+ const [isLoading, setIsLoading] = useState(true);
50
+ const [isVisible, setIsVisible] = useState(false);
49
51
 
50
52
  const fetchDocuments = useCallback(() => {
51
- if (UID) {
52
- const sort_on = ['documentation_sorting', 'sortable_title'];
53
- const sort_order = ['ascending', 'ascending'];
53
+ const sort_on = ['documentation_sorting', 'sortable_title'];
54
+ const sort_order = ['ascending', 'ascending'];
54
55
 
56
+ if (UID) {
55
57
  setIsLoading(true);
58
+ const searchParams = {
59
+ portal_type: 'TechnicalLibrary',
60
+ path: 'en/technical-library',
61
+ sort_on: sort_on,
62
+ sort_order: sort_order,
63
+ b_size: 99999,
64
+ };
65
+
66
+ if (contentType === 'DataSet') {
67
+ searchParams.associated_datasets = UID;
68
+ } else if (contentType === 'Product') {
69
+ searchParams.associated_products = UID;
70
+ }
71
+
56
72
  dispatch(
57
- searchContent(
58
- 'en/technical-library',
59
- {
60
- portal_type: 'TechnicalLibrary',
61
- path: 'en/technical-library',
62
- associated_datasets: UID,
63
- sort_on: sort_on,
64
- sort_order: sort_order,
65
- b_size: 99999,
66
- },
67
- id,
68
- ),
73
+ searchContent('en/technical-library', searchParams, id),
69
74
  ).finally(() => setIsLoading(false));
70
75
  }
71
- }, [id, UID, dispatch]);
76
+ }, [id, UID, contentType, dispatch]);
72
77
 
73
78
  const handleDragEnd = useCallback(
74
79
  (result) => {
@@ -86,8 +91,8 @@ const OrderDocumentsWidget = (props) => {
86
91
  [documentsList, onChange, id],
87
92
  );
88
93
 
89
- const memoizedList = useMemo(
90
- () => (
94
+ const memoizedList = useMemo(() => {
95
+ return (
91
96
  <DragDropContext onDragEnd={handleDragEnd}>
92
97
  <Droppable droppableId="documents">
93
98
  {(provided) => (
@@ -110,9 +115,8 @@ const OrderDocumentsWidget = (props) => {
110
115
  )}
111
116
  </Droppable>
112
117
  </DragDropContext>
113
- ),
114
- [documentsList, handleDragEnd],
115
- );
118
+ );
119
+ }, [documentsList, handleDragEnd]);
116
120
 
117
121
  useEffect(() => {
118
122
  fetchDocuments();
@@ -130,9 +134,9 @@ const OrderDocumentsWidget = (props) => {
130
134
  const newItems = receivedDocumentsList.filter(
131
135
  (item) => !value.items.some((item2) => item2.id === item.id),
132
136
  );
133
- setDocumentsList([...value.items, ...newItems]);
137
+ setDocumentsList([...value?.items, ...newItems]);
134
138
  } else {
135
- setDocumentsList(value.items);
139
+ setDocumentsList(value?.items);
136
140
  }
137
141
  }
138
142
  }, [searchSubrequests, id, value]);
@@ -143,6 +147,18 @@ const OrderDocumentsWidget = (props) => {
143
147
  draggable={true}
144
148
  className="drag-drop-list-widget"
145
149
  >
150
+ {contentType === 'Product' && (
151
+ <Button
152
+ basic
153
+ size="small"
154
+ type="button"
155
+ onClick={() => setIsVisible((prev) => !prev)}
156
+ aria-label="Toggle document list visibility"
157
+ className="order-docs-button"
158
+ >
159
+ {isVisible ? 'Hide' : 'Show'}
160
+ </Button>
161
+ )}
146
162
  <div className="order-documents-area">
147
163
  <div className="documents-list-header">
148
164
  <div>Technical Document Title</div>
@@ -150,7 +166,7 @@ const OrderDocumentsWidget = (props) => {
150
166
  {isLoading ? (
151
167
  <div className="loading-documents">Loading documents...</div>
152
168
  ) : (
153
- memoizedList
169
+ (contentType === 'DataSet' || isVisible) && memoizedList
154
170
  )}
155
171
  </div>
156
172
  </FormFieldWrapper>
@@ -55,7 +55,7 @@ describe('OrderDocumentsWidget', () => {
55
55
  <MemoryRouter>
56
56
  <OrderDocumentsWidget
57
57
  id="testId"
58
- formData={{ UID: 'someUID' }}
58
+ formData={{ UID: 'someUID', '@type': 'DataSet' }}
59
59
  onChange={mockOnChange}
60
60
  value={{ items: [] }}
61
61
  />
@@ -80,7 +80,7 @@ describe('OrderDocumentsWidget', () => {
80
80
  id="testId"
81
81
  formData={{ UID: 'someUID' }}
82
82
  onChange={mockOnChange}
83
- value={{ items: [] }}
83
+ value={{ items: [], '@type': 'DataSet' }}
84
84
  />
85
85
  </MemoryRouter>
86
86
  </Provider>,
@@ -96,7 +96,7 @@ describe('OrderDocumentsWidget', () => {
96
96
  <MemoryRouter>
97
97
  <OrderDocumentsWidget
98
98
  id="testId"
99
- formData={{ UID: 'someUID' }}
99
+ formData={{ UID: 'someUID', '@type': 'DataSet' }}
100
100
  onChange={mockOnChange}
101
101
  value={{
102
102
  items: [
@@ -151,7 +151,7 @@ describe('OrderDocumentsWidget', () => {
151
151
  <MemoryRouter>
152
152
  <OrderDocumentsWidget
153
153
  id="testId"
154
- formData={{ UID: 'someUID' }}
154
+ formData={{ UID: 'someUID', '@type': 'DataSet' }}
155
155
  onChange={mockOnChange}
156
156
  value={{ items: [] }}
157
157
  />
@@ -190,7 +190,7 @@ describe('OrderDocumentsWidget', () => {
190
190
  <MemoryRouter>
191
191
  <OrderDocumentsWidget
192
192
  id="testId"
193
- formData={{ UID: 'someUID' }}
193
+ formData={{ UID: 'someUID', '@type': 'DataSet' }}
194
194
  onChange={mockOnChange}
195
195
  value={{ items: [] }}
196
196
  />
@@ -206,4 +206,35 @@ describe('OrderDocumentsWidget', () => {
206
206
  expect(screen.getByText('Document 3')).toBeInTheDocument();
207
207
  expect(screen.getByText('Document 4')).toBeInTheDocument();
208
208
  });
209
+
210
+ it('renders the widget with documents', async () => {
211
+ act(() => {
212
+ render(
213
+ <Provider store={store}>
214
+ <MemoryRouter>
215
+ <OrderDocumentsWidget
216
+ id="testId"
217
+ formData={{ UID: 'someUID', '@type': 'Product' }}
218
+ onChange={mockOnChange}
219
+ value={{ items: [] }}
220
+ />
221
+ </MemoryRouter>
222
+ </Provider>,
223
+ );
224
+ });
225
+ await Promise.resolve();
226
+
227
+ expect(screen.getByText('Show')).toBeInTheDocument();
228
+ fireEvent.click(screen.getByText('Show'));
229
+
230
+ expect(screen.getByText('Technical Document Title')).toBeInTheDocument();
231
+ expect(screen.getByText('Document 1')).toBeInTheDocument();
232
+ expect(screen.getByText('Document 2')).toBeInTheDocument();
233
+ expect(screen.getByText('Document 3')).toBeInTheDocument();
234
+
235
+ expect(screen.getByText('Hide')).toBeInTheDocument();
236
+ fireEvent.click(screen.getByText('Hide'));
237
+
238
+ expect(screen.getByText('Technical Document Title')).toBeInTheDocument();
239
+ });
209
240
  });
@@ -1,7 +1,7 @@
1
1
  .order-documents-area {
2
2
  width: 100%;
3
3
  max-width: 800px;
4
- margin: 16px auto;
4
+ margin: 16px 0;
5
5
  border: 1px solid #e0e0e0;
6
6
  border-radius: 8px;
7
7
  overflow: hidden;
@@ -41,6 +41,7 @@
41
41
  background-color: #ffffff;
42
42
  transition: background-color 0.2s ease;
43
43
  cursor: grab;
44
+ box-shadow: none;
44
45
  }
45
46
 
46
47
  .document-item:last-child {
@@ -64,4 +65,26 @@
64
65
  background-color: #f0f8ff;
65
66
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
66
67
  }
68
+
69
+ .document-item {
70
+ .ui.basic.icon.button[aria-label='Drag handle'] {
71
+ box-shadow: none;
72
+ }
73
+ }
74
+ }
75
+
76
+ .field-wrapper-technical_documents_order {
77
+ .ui.basic.button.order-docs-button {
78
+ display: inline-block;
79
+ padding: 7px 10px;
80
+ border: 2px solid #007eb1;
81
+ margin-top: 20px;
82
+ border-radius: 10px;
83
+ color: #007eb1 !important;
84
+ cursor: pointer;
85
+ opacity: 0.8;
86
+ box-shadow: none;
87
+ max-width: 200px;
88
+ text-align: center;
89
+ }
67
90
  }