@eeacms/volto-cca-policy 1.0.2 → 1.0.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.
- package/CHANGELOG.md +231 -16
- package/jest-addon.config.js +4 -2
- package/package.json +1 -1
- package/src/components/Search/NavigatorCatalogue/NavigatorCatalogueCardItem.jsx +228 -0
- package/src/components/Search/NavigatorCatalogue/NavigatorCatalogueCardItem.test.jsx +167 -0
- package/src/components/Search/NavigatorCatalogue/NavigatorCatalogueContentView.jsx +189 -0
- package/src/components/Search/NavigatorCatalogue/NavigatorCatalogueMapView.jsx +11 -0
- package/src/components/Search/NavigatorCatalogue/utils.js +101 -0
- package/src/components/Search/NavigatorCatalogue/utils.test.js +132 -0
- package/src/components/Search/NavigatorGuide/NavigatorGuideContentView.jsx +409 -0
- package/src/components/Search/NavigatorGuide/NavigatorGuideLayout.jsx +8 -0
- package/src/components/index.js +2 -0
- package/src/components/theme/CompareTools/CompareToolsPanel.jsx +151 -0
- package/src/components/theme/CompareTools/CompareToolsPanel.test.jsx +96 -0
- package/src/components/theme/CompareTools/CompareToolsView.jsx +468 -0
- package/src/components/theme/CompareTools/utils.js +111 -0
- package/src/components/theme/CompareTools/utils.test.jsx +217 -0
- package/src/components/theme/Views/ExtendedToolView.jsx +201 -201
- package/src/components/theme/Views/ExtendedToolView.test.jsx +420 -58
- package/src/customizations/volto/reducers/breadcrumbs/breadcrumbs.js +1 -1
- package/src/helpers/Utils.jsx +29 -0
- package/src/helpers/index.js +3 -0
- package/src/index.js +28 -2
- package/src/search/index.js +6 -0
- package/src/search/navigator_catalogue/config.js +89 -0
- package/src/search/navigator_catalogue/facets.js +86 -0
- package/src/search/navigator_catalogue/views.js +28 -0
- package/src/search/navigator_guide/config.js +70 -0
- package/src/search/navigator_guide/facets.js +31 -0
- package/src/search/navigator_guide/guideSteps.js +93 -0
- package/src/slate-styles.less +4 -0
- package/src/state.js +1 -0
- package/theme/elements/button.overrides +0 -11
- package/theme/elements/label.overrides +15 -0
- package/theme/globals/blocks.less +3 -2
- package/theme/globals/navigator.less +967 -0
- package/theme/globals/site.overrides +5 -0
- package/theme/tokens/colors.less +2 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useAtom } from 'jotai';
|
|
3
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
4
|
+
import { useHistory, useLocation } from 'react-router-dom';
|
|
5
|
+
import {
|
|
6
|
+
Container,
|
|
7
|
+
Icon,
|
|
8
|
+
Loader,
|
|
9
|
+
Message,
|
|
10
|
+
Table,
|
|
11
|
+
Button,
|
|
12
|
+
} from 'semantic-ui-react';
|
|
13
|
+
import Helmet from '@plone/volto/helpers/Helmet/Helmet';
|
|
14
|
+
import BodyClass from '@plone/volto/helpers/BodyClass/BodyClass';
|
|
15
|
+
import UniversalLink from '@plone/volto/components/manage/UniversalLink/UniversalLink';
|
|
16
|
+
import { GET_BREADCRUMBS } from '@plone/volto/constants/ActionTypes';
|
|
17
|
+
import config from '@plone/volto/registry';
|
|
18
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
19
|
+
import BannerTitle from '../BannerTitle/BannerTitle';
|
|
20
|
+
import {
|
|
21
|
+
MAX_COMPARE_TOOLS,
|
|
22
|
+
compareToolsAtom,
|
|
23
|
+
fetchResultsByUid,
|
|
24
|
+
getCompareToolUid,
|
|
25
|
+
getPathname,
|
|
26
|
+
} from './utils';
|
|
27
|
+
import {
|
|
28
|
+
asArray,
|
|
29
|
+
exportComparisonTable,
|
|
30
|
+
formatFunctionalityScore,
|
|
31
|
+
getLocalizedLandingPageURL,
|
|
32
|
+
} from '../../Search/NavigatorCatalogue/utils';
|
|
33
|
+
|
|
34
|
+
const messages = defineMessages({
|
|
35
|
+
compareTools: {
|
|
36
|
+
id: 'Compare tools',
|
|
37
|
+
defaultMessage: 'Compare tools',
|
|
38
|
+
},
|
|
39
|
+
navigator: {
|
|
40
|
+
id: 'Navigator',
|
|
41
|
+
defaultMessage: 'Navigator',
|
|
42
|
+
},
|
|
43
|
+
comparingTools: {
|
|
44
|
+
id: 'Comparing {count} {count, plural, one {tool} other {tools}}',
|
|
45
|
+
defaultMessage:
|
|
46
|
+
'Comparing {count} {count, plural, one {tool} other {tools}}',
|
|
47
|
+
},
|
|
48
|
+
notEnoughTools: {
|
|
49
|
+
id: 'At least two valid tools are required for comparison. Select tools from the Navigator Catalogue.',
|
|
50
|
+
defaultMessage:
|
|
51
|
+
'At least two valid tools are required for comparison. Select tools from the Navigator Catalogue.',
|
|
52
|
+
},
|
|
53
|
+
toolsSkipped: {
|
|
54
|
+
id: 'Some selected tools could not be loaded and were skipped.',
|
|
55
|
+
defaultMessage: 'Some selected tools could not be loaded and were skipped.',
|
|
56
|
+
},
|
|
57
|
+
allToolsFailed: {
|
|
58
|
+
id: 'The selected tools could not be loaded. Return to the Navigator Catalogue and select them again.',
|
|
59
|
+
defaultMessage:
|
|
60
|
+
'The selected tools could not be loaded. Return to the Navigator Catalogue and select them again.',
|
|
61
|
+
},
|
|
62
|
+
criteria: {
|
|
63
|
+
id: 'Criteria',
|
|
64
|
+
defaultMessage: 'Criteria',
|
|
65
|
+
},
|
|
66
|
+
openTool: {
|
|
67
|
+
id: 'Open tool',
|
|
68
|
+
defaultMessage: 'Open tool',
|
|
69
|
+
},
|
|
70
|
+
backToResults: {
|
|
71
|
+
id: 'Back to results',
|
|
72
|
+
defaultMessage: 'Back to results',
|
|
73
|
+
},
|
|
74
|
+
exportTable: {
|
|
75
|
+
id: 'Export table',
|
|
76
|
+
defaultMessage: 'Export table',
|
|
77
|
+
},
|
|
78
|
+
usability: {
|
|
79
|
+
id: 'Usability',
|
|
80
|
+
defaultMessage: 'Usability',
|
|
81
|
+
},
|
|
82
|
+
functionality: {
|
|
83
|
+
id: 'Functionality',
|
|
84
|
+
defaultMessage: 'Functionality',
|
|
85
|
+
},
|
|
86
|
+
spatialScale: {
|
|
87
|
+
id: 'Spatial scale',
|
|
88
|
+
defaultMessage: 'Spatial scale',
|
|
89
|
+
},
|
|
90
|
+
outputType: {
|
|
91
|
+
id: 'Output type',
|
|
92
|
+
defaultMessage: 'Output type',
|
|
93
|
+
},
|
|
94
|
+
adaptationSupportCycleStep: {
|
|
95
|
+
id: 'Adaptation support cycle step',
|
|
96
|
+
defaultMessage: 'Adaptation support cycle step',
|
|
97
|
+
},
|
|
98
|
+
sector: {
|
|
99
|
+
id: 'Sector',
|
|
100
|
+
defaultMessage: 'Sector',
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const getToolField = (tool, field) =>
|
|
105
|
+
tool.result?.[field] ?? tool.result?._result?.[field];
|
|
106
|
+
|
|
107
|
+
const getToolFieldDisplay = (tool, field) =>
|
|
108
|
+
asArray(getToolField(tool, field)).join(', ') || '—';
|
|
109
|
+
|
|
110
|
+
const getCompareUids = (search) => {
|
|
111
|
+
const params = new URLSearchParams(search);
|
|
112
|
+
|
|
113
|
+
return [...new Set(params.getAll('uid').filter(Boolean))].slice(
|
|
114
|
+
0,
|
|
115
|
+
MAX_COMPARE_TOOLS,
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const getReturnURL = (search) => {
|
|
120
|
+
const returnURL = new URLSearchParams(search).get('return_url');
|
|
121
|
+
|
|
122
|
+
return returnURL?.startsWith('/') && !returnURL.startsWith('//')
|
|
123
|
+
? returnURL
|
|
124
|
+
: '';
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const getToolTitle = (result, fallback) =>
|
|
128
|
+
result?.title || result?._result?.title?.raw || fallback;
|
|
129
|
+
|
|
130
|
+
const getToolHref = (result) => result?.href || result?._result?.id?.raw || '';
|
|
131
|
+
|
|
132
|
+
const getTools = async (uids, registry) => {
|
|
133
|
+
try {
|
|
134
|
+
const results = await fetchResultsByUid(uids, registry);
|
|
135
|
+
const resultsByUid = new Map(
|
|
136
|
+
results.map((result) => [getCompareToolUid(result), result]),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
return uids.map((uid) => {
|
|
140
|
+
const result = resultsByUid.get(uid);
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
id: uid,
|
|
144
|
+
title: getToolTitle(result, uid),
|
|
145
|
+
href: getToolHref(result),
|
|
146
|
+
result,
|
|
147
|
+
error: !result,
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
} catch {
|
|
151
|
+
return uids.map((uid) => ({
|
|
152
|
+
id: uid,
|
|
153
|
+
title: uid,
|
|
154
|
+
error: true,
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const CompareToolsView = () => {
|
|
160
|
+
const intl = useIntl();
|
|
161
|
+
const dispatch = useDispatch();
|
|
162
|
+
const history = useHistory();
|
|
163
|
+
const location = useLocation();
|
|
164
|
+
const currentLang = useSelector((state) => state.intl.locale);
|
|
165
|
+
const [, setSelectedTools] = useAtom(compareToolsAtom);
|
|
166
|
+
const ids = React.useMemo(
|
|
167
|
+
() => getCompareUids(location.search),
|
|
168
|
+
[location.search],
|
|
169
|
+
);
|
|
170
|
+
const registry = config.settings.searchlib;
|
|
171
|
+
const appConfig = registry.searchui.navigatorCatalogueSearch;
|
|
172
|
+
const landingPageURL = getLocalizedLandingPageURL(appConfig, currentLang);
|
|
173
|
+
const compareToolsTitle = intl.formatMessage(messages.compareTools);
|
|
174
|
+
const returnURL =
|
|
175
|
+
location.state?.returnURL ||
|
|
176
|
+
getReturnURL(location.search) ||
|
|
177
|
+
landingPageURL;
|
|
178
|
+
const backURL =
|
|
179
|
+
getPathname(returnURL) === getPathname(landingPageURL)
|
|
180
|
+
? returnURL
|
|
181
|
+
: landingPageURL;
|
|
182
|
+
const breadcrumbParentTitle = intl.formatMessage(messages.navigator);
|
|
183
|
+
const breadcrumbParentURL = backURL;
|
|
184
|
+
const [tools, setTools] = React.useState([]);
|
|
185
|
+
const [isLoading, setIsLoading] = React.useState(false);
|
|
186
|
+
|
|
187
|
+
React.useEffect(() => {
|
|
188
|
+
dispatch({ type: `${GET_BREADCRUMBS}_PENDING` });
|
|
189
|
+
dispatch({
|
|
190
|
+
type: `${GET_BREADCRUMBS}_SUCCESS`,
|
|
191
|
+
manual: true,
|
|
192
|
+
result: {
|
|
193
|
+
root: `/${currentLang}`,
|
|
194
|
+
items: [
|
|
195
|
+
{
|
|
196
|
+
title: breadcrumbParentTitle,
|
|
197
|
+
'@id': breadcrumbParentURL,
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
title: compareToolsTitle,
|
|
201
|
+
'@id': location.pathname,
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
}, [
|
|
207
|
+
breadcrumbParentTitle,
|
|
208
|
+
breadcrumbParentURL,
|
|
209
|
+
compareToolsTitle,
|
|
210
|
+
currentLang,
|
|
211
|
+
dispatch,
|
|
212
|
+
location.pathname,
|
|
213
|
+
]);
|
|
214
|
+
|
|
215
|
+
React.useEffect(() => {
|
|
216
|
+
let ignore = false;
|
|
217
|
+
|
|
218
|
+
const loadTools = async () => {
|
|
219
|
+
setIsLoading(true);
|
|
220
|
+
const results = await getTools(ids, registry);
|
|
221
|
+
|
|
222
|
+
if (!ignore) {
|
|
223
|
+
setTools(results);
|
|
224
|
+
setIsLoading(false);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
if (ids.length >= 2) {
|
|
229
|
+
loadTools();
|
|
230
|
+
} else {
|
|
231
|
+
setTools([]);
|
|
232
|
+
setIsLoading(false);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return () => {
|
|
236
|
+
ignore = true;
|
|
237
|
+
};
|
|
238
|
+
}, [ids, registry]);
|
|
239
|
+
|
|
240
|
+
const failedTools = tools.filter((tool) => tool.error);
|
|
241
|
+
const visibleTools = tools.filter((tool) => !tool.error);
|
|
242
|
+
const visibleToolsCount = visibleTools.length;
|
|
243
|
+
const hasLoadedRequestedTools = tools.length === ids.length;
|
|
244
|
+
const hasEnoughTools = visibleToolsCount >= 2;
|
|
245
|
+
const allRequestedToolsFailed =
|
|
246
|
+
ids.length >= 2 &&
|
|
247
|
+
hasLoadedRequestedTools &&
|
|
248
|
+
failedTools.length === ids.length;
|
|
249
|
+
const someRequestedToolsFailed =
|
|
250
|
+
failedTools.length > 0 && !allRequestedToolsFailed;
|
|
251
|
+
const showNotEnoughTools =
|
|
252
|
+
ids.length < 2 ||
|
|
253
|
+
(!isLoading &&
|
|
254
|
+
hasLoadedRequestedTools &&
|
|
255
|
+
!hasEnoughTools &&
|
|
256
|
+
!allRequestedToolsFailed);
|
|
257
|
+
|
|
258
|
+
const removeTool = (toolId) => {
|
|
259
|
+
const params = new URLSearchParams(location.search);
|
|
260
|
+
const remainingIds = ids.filter((uid) => uid !== toolId);
|
|
261
|
+
|
|
262
|
+
params.delete('uid');
|
|
263
|
+
remainingIds.forEach((uid) => params.append('uid', uid));
|
|
264
|
+
|
|
265
|
+
setSelectedTools((selectedTools) =>
|
|
266
|
+
selectedTools.filter((tool) => tool.uid !== toolId),
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
history.push({
|
|
270
|
+
pathname: location.pathname,
|
|
271
|
+
search: params.toString() ? `?${params.toString()}` : '',
|
|
272
|
+
hash: location.hash,
|
|
273
|
+
state: location.state,
|
|
274
|
+
});
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
return (
|
|
278
|
+
<div className="navigator-catalogue-compare-view">
|
|
279
|
+
<Helmet title={compareToolsTitle} />
|
|
280
|
+
<BodyClass className="navigator-catalogue-compare-page" />
|
|
281
|
+
<BannerTitle content={{ title: compareToolsTitle }} />
|
|
282
|
+
|
|
283
|
+
<Container>
|
|
284
|
+
<div className="compare-page-header">
|
|
285
|
+
<h3>
|
|
286
|
+
{intl.formatMessage(messages.comparingTools, {
|
|
287
|
+
count: visibleToolsCount,
|
|
288
|
+
})}
|
|
289
|
+
</h3>
|
|
290
|
+
|
|
291
|
+
<div className="compare-page-actions">
|
|
292
|
+
<Button onClick={() => history.push(backURL)}>
|
|
293
|
+
<Icon className="ri-arrow-left-line" />
|
|
294
|
+
{intl.formatMessage(messages.backToResults)}
|
|
295
|
+
</Button>
|
|
296
|
+
<Button
|
|
297
|
+
className="primary inverted"
|
|
298
|
+
disabled={!hasEnoughTools}
|
|
299
|
+
onClick={() => exportComparisonTable(visibleTools, getToolField)}
|
|
300
|
+
>
|
|
301
|
+
<Icon className="ri-download-2-line" />
|
|
302
|
+
{intl.formatMessage(messages.exportTable)}
|
|
303
|
+
</Button>
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
|
|
307
|
+
{showNotEnoughTools && (
|
|
308
|
+
<Message>{intl.formatMessage(messages.notEnoughTools)}</Message>
|
|
309
|
+
)}
|
|
310
|
+
|
|
311
|
+
{isLoading && <Loader active inline="centered" />}
|
|
312
|
+
|
|
313
|
+
{!isLoading && allRequestedToolsFailed && (
|
|
314
|
+
<Message error>{intl.formatMessage(messages.allToolsFailed)}</Message>
|
|
315
|
+
)}
|
|
316
|
+
|
|
317
|
+
{!isLoading && someRequestedToolsFailed && (
|
|
318
|
+
<Message warning>{intl.formatMessage(messages.toolsSkipped)}</Message>
|
|
319
|
+
)}
|
|
320
|
+
|
|
321
|
+
{!isLoading && hasEnoughTools && (
|
|
322
|
+
<Table celled>
|
|
323
|
+
<Table.Header>
|
|
324
|
+
<Table.Row>
|
|
325
|
+
<Table.HeaderCell>
|
|
326
|
+
<span className="compare-criteria-label">
|
|
327
|
+
{intl.formatMessage(messages.criteria)}
|
|
328
|
+
</span>
|
|
329
|
+
</Table.HeaderCell>
|
|
330
|
+
{visibleTools.map((tool) => (
|
|
331
|
+
<Table.HeaderCell key={tool.id}>
|
|
332
|
+
<div className="compare-tool-header">
|
|
333
|
+
<div className="compare-tool-title-row">
|
|
334
|
+
<div
|
|
335
|
+
className="navigator-tool-icon medium"
|
|
336
|
+
aria-hidden="true"
|
|
337
|
+
>
|
|
338
|
+
<Icon className="ri-file-line" />
|
|
339
|
+
</div>
|
|
340
|
+
<div className="compare-tool-title" title={tool.title}>
|
|
341
|
+
{tool.title}
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
<div className="compare-tool-actions">
|
|
345
|
+
{tool.href && (
|
|
346
|
+
<UniversalLink
|
|
347
|
+
className="ui button primary icon compare-tool-open"
|
|
348
|
+
href={tool.href}
|
|
349
|
+
>
|
|
350
|
+
{intl.formatMessage(messages.openTool)}
|
|
351
|
+
<Icon className="ri-external-link-line" />
|
|
352
|
+
</UniversalLink>
|
|
353
|
+
)}
|
|
354
|
+
<Button
|
|
355
|
+
className="icon compare-tool-clear"
|
|
356
|
+
onClick={() => removeTool(tool.id)}
|
|
357
|
+
>
|
|
358
|
+
<Icon className="ri-close-line" />
|
|
359
|
+
</Button>
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
</Table.HeaderCell>
|
|
363
|
+
))}
|
|
364
|
+
</Table.Row>
|
|
365
|
+
</Table.Header>
|
|
366
|
+
<Table.Body>
|
|
367
|
+
<Table.Row>
|
|
368
|
+
<Table.Cell>
|
|
369
|
+
<div className="compare-criteria">
|
|
370
|
+
<div className="compare-criteria-title">
|
|
371
|
+
{intl.formatMessage(messages.usability)}
|
|
372
|
+
</div>
|
|
373
|
+
</div>
|
|
374
|
+
</Table.Cell>
|
|
375
|
+
{visibleTools.map((tool) => (
|
|
376
|
+
<Table.Cell key={`usability-${tool.id}`}>
|
|
377
|
+
<div className="usability-value">
|
|
378
|
+
{getToolFieldDisplay(tool, 'accessibility_and_usability')}
|
|
379
|
+
</div>
|
|
380
|
+
</Table.Cell>
|
|
381
|
+
))}
|
|
382
|
+
</Table.Row>
|
|
383
|
+
<Table.Row>
|
|
384
|
+
<Table.Cell>
|
|
385
|
+
<div className="compare-criteria">
|
|
386
|
+
<div className="compare-criteria-title">
|
|
387
|
+
{intl.formatMessage(messages.functionality)}
|
|
388
|
+
</div>
|
|
389
|
+
</div>
|
|
390
|
+
</Table.Cell>
|
|
391
|
+
{visibleTools.map((tool) => (
|
|
392
|
+
<Table.Cell key={`functionality-${tool.id}`}>
|
|
393
|
+
<div className="functionality-value">
|
|
394
|
+
{formatFunctionalityScore(
|
|
395
|
+
getToolField(tool, 'functionality'),
|
|
396
|
+
)}
|
|
397
|
+
</div>
|
|
398
|
+
</Table.Cell>
|
|
399
|
+
))}
|
|
400
|
+
</Table.Row>
|
|
401
|
+
<Table.Row>
|
|
402
|
+
<Table.Cell>
|
|
403
|
+
<div className="compare-criteria">
|
|
404
|
+
<div className="compare-criteria-title">
|
|
405
|
+
{intl.formatMessage(messages.spatialScale)}
|
|
406
|
+
</div>
|
|
407
|
+
</div>
|
|
408
|
+
</Table.Cell>
|
|
409
|
+
{visibleTools.map((tool) => (
|
|
410
|
+
<Table.Cell key={`spatial-scale-${tool.id}`}>
|
|
411
|
+
{getToolFieldDisplay(tool, 'cca_geographical_scale')}
|
|
412
|
+
</Table.Cell>
|
|
413
|
+
))}
|
|
414
|
+
</Table.Row>
|
|
415
|
+
<Table.Row>
|
|
416
|
+
<Table.Cell>
|
|
417
|
+
<div className="compare-criteria">
|
|
418
|
+
<div className="compare-criteria-title">
|
|
419
|
+
{intl.formatMessage(messages.outputType)}
|
|
420
|
+
</div>
|
|
421
|
+
</div>
|
|
422
|
+
</Table.Cell>
|
|
423
|
+
{visibleTools.map((tool) => (
|
|
424
|
+
<Table.Cell key={`output-type-${tool.id}`}>
|
|
425
|
+
{getToolFieldDisplay(tool, 'cca_type_of_outputs')}
|
|
426
|
+
</Table.Cell>
|
|
427
|
+
))}
|
|
428
|
+
</Table.Row>
|
|
429
|
+
<Table.Row>
|
|
430
|
+
<Table.Cell>
|
|
431
|
+
<div className="compare-criteria">
|
|
432
|
+
<div className="compare-criteria-title">
|
|
433
|
+
{intl.formatMessage(messages.adaptationSupportCycleStep)}
|
|
434
|
+
</div>
|
|
435
|
+
</div>
|
|
436
|
+
</Table.Cell>
|
|
437
|
+
{visibleTools.map((tool) => (
|
|
438
|
+
<Table.Cell key={`adaptation-support-cycle-step-${tool.id}`}>
|
|
439
|
+
{getToolFieldDisplay(
|
|
440
|
+
tool,
|
|
441
|
+
'cca_adaptation_support_cycle_step',
|
|
442
|
+
)}
|
|
443
|
+
</Table.Cell>
|
|
444
|
+
))}
|
|
445
|
+
</Table.Row>
|
|
446
|
+
<Table.Row>
|
|
447
|
+
<Table.Cell>
|
|
448
|
+
<div className="compare-criteria">
|
|
449
|
+
<div className="compare-criteria-title">
|
|
450
|
+
{intl.formatMessage(messages.sector)}
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
</Table.Cell>
|
|
454
|
+
{visibleTools.map((tool) => (
|
|
455
|
+
<Table.Cell key={`sector-${tool.id}`}>
|
|
456
|
+
{getToolFieldDisplay(tool, 'cca_adaptation_sectors')}
|
|
457
|
+
</Table.Cell>
|
|
458
|
+
))}
|
|
459
|
+
</Table.Row>
|
|
460
|
+
</Table.Body>
|
|
461
|
+
</Table>
|
|
462
|
+
)}
|
|
463
|
+
</Container>
|
|
464
|
+
</div>
|
|
465
|
+
);
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
export default CompareToolsView;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { useAtom } from 'jotai';
|
|
2
|
+
import { atomWithStorage } from 'jotai/utils';
|
|
3
|
+
import { applyConfigurationSchema, rebind } from '@eeacms/search';
|
|
4
|
+
import runRequest from '@eeacms/search/lib/runRequest';
|
|
5
|
+
import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
|
|
6
|
+
import { getComparePageURL } from '../../Search/NavigatorCatalogue/utils';
|
|
7
|
+
|
|
8
|
+
export const MAX_COMPARE_TOOLS = 4;
|
|
9
|
+
export const compareToolsAtom = atomWithStorage('cca-compare-tools', []);
|
|
10
|
+
const searchAppName = 'navigatorCatalogueSearch';
|
|
11
|
+
|
|
12
|
+
const getRawValue = (value) =>
|
|
13
|
+
value?.raw !== undefined ? value.raw : value || '';
|
|
14
|
+
|
|
15
|
+
export const getCompareToolUid = (result) =>
|
|
16
|
+
getRawValue(result.cca_uid || result.UID || result._result?.cca_uid);
|
|
17
|
+
|
|
18
|
+
export const getCompareToolTitle = (result) => result.title || '';
|
|
19
|
+
|
|
20
|
+
export const getPathname = (url) => {
|
|
21
|
+
if (!url) return '';
|
|
22
|
+
|
|
23
|
+
const pathname = flattenToAppURL(url).split('#')[0].split('?')[0];
|
|
24
|
+
|
|
25
|
+
return pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const useCompareTools = (compareTool) => {
|
|
29
|
+
const [selectedTools, setSelectedTools] = useAtom(compareToolsAtom);
|
|
30
|
+
const isSelected = selectedTools.some(
|
|
31
|
+
(tool) => tool.uid === compareTool?.uid,
|
|
32
|
+
);
|
|
33
|
+
const isLimitReached =
|
|
34
|
+
selectedTools.length >= MAX_COMPARE_TOOLS && !isSelected;
|
|
35
|
+
|
|
36
|
+
const setSelected = (selected) => {
|
|
37
|
+
if (!compareTool?.uid) return;
|
|
38
|
+
|
|
39
|
+
setSelectedTools((tools) => {
|
|
40
|
+
if (!selected) {
|
|
41
|
+
return tools.filter((tool) => tool.uid !== compareTool.uid);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (
|
|
45
|
+
tools.some((tool) => tool.uid === compareTool.uid) ||
|
|
46
|
+
tools.length >= MAX_COMPARE_TOOLS
|
|
47
|
+
) {
|
|
48
|
+
return tools;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return [...tools, compareTool];
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
isSelected,
|
|
57
|
+
isLimitReached,
|
|
58
|
+
setSelected,
|
|
59
|
+
toggle: () => setSelected(!isSelected),
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const getCompareLocation = (
|
|
64
|
+
tools,
|
|
65
|
+
appConfig,
|
|
66
|
+
returnURL,
|
|
67
|
+
currentLang = 'en',
|
|
68
|
+
) => {
|
|
69
|
+
const params = new URLSearchParams();
|
|
70
|
+
|
|
71
|
+
tools.slice(0, MAX_COMPARE_TOOLS).forEach((tool) => {
|
|
72
|
+
if (tool.uid) {
|
|
73
|
+
params.append('uid', tool.uid);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const query = params.toString();
|
|
78
|
+
const path = getComparePageURL(appConfig, currentLang);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
pathname: path,
|
|
82
|
+
search: query ? `?${query}` : '',
|
|
83
|
+
state: { returnURL },
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const fetchResultsByUid = async (uids, registry) => {
|
|
88
|
+
const appConfig = applyConfigurationSchema(
|
|
89
|
+
rebind(registry.searchui[searchAppName]),
|
|
90
|
+
);
|
|
91
|
+
const response = await runRequest(
|
|
92
|
+
{
|
|
93
|
+
...(appConfig.index_name ? { index: appConfig.index_name } : {}),
|
|
94
|
+
size: uids.length,
|
|
95
|
+
query: {
|
|
96
|
+
bool: {
|
|
97
|
+
minimum_should_match: 1,
|
|
98
|
+
should: [
|
|
99
|
+
{ terms: { 'cca_uid.keyword': uids } },
|
|
100
|
+
{ terms: { cca_uid: uids } },
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
appConfig,
|
|
106
|
+
);
|
|
107
|
+
const hits = response.body?.hits?.hits || [];
|
|
108
|
+
const Model = registry.resolve[appConfig.resultItemModel.factory];
|
|
109
|
+
|
|
110
|
+
return hits.map((hit) => new Model(hit, appConfig));
|
|
111
|
+
};
|