io-sanita-theme 2.19.0 → 2.20.1

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 (30) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/locales/de/LC_MESSAGES/volto.po +10 -0
  3. package/locales/en/LC_MESSAGES/volto.po +10 -0
  4. package/locales/es/LC_MESSAGES/volto.po +10 -0
  5. package/locales/fr/LC_MESSAGES/volto.po +10 -0
  6. package/locales/it/LC_MESSAGES/volto.po +10 -0
  7. package/locales/volto.pot +11 -1
  8. package/package.json +1 -1
  9. package/src/actions/search.js +12 -6
  10. package/src/components/Blocks/Listing/Table/TableTemplate.jsx +6 -2
  11. package/src/components/Blocks/Listing/Table/table-templates.scss +5 -0
  12. package/src/components/Blocks/QuickSearch/Body.jsx +8 -1
  13. package/src/components/OverlayLoading/OverlayLoading.jsx +18 -0
  14. package/src/components/OverlayLoading/overlayLoading.scss +12 -0
  15. package/src/components/View/AggregationPage/AggregationPage.jsx +9 -2
  16. package/src/components/index.js +2 -0
  17. package/src/components/layout/Header/HeaderSearch/SearchModal.jsx +7 -6
  18. package/src/components/layout/Header/HeaderSearch/searchModal.scss +0 -13
  19. package/src/config/blocks/index.js +2 -0
  20. package/src/customizations/volto/components/manage/Blocks/Search/SearchBlockView.jsx +50 -2
  21. package/src/customizations/volto/components/manage/Blocks/Search/components/SearchDetails.jsx +1 -1
  22. package/src/customizations/volto/components/manage/Blocks/Search/components/SelectFacet.jsx +2 -2
  23. package/src/customizations/volto/components/manage/Blocks/Search/components/SortOn.jsx +85 -0
  24. package/src/customizations/volto/components/manage/Blocks/Search/layout/LeftColumnFacets.jsx +33 -2
  25. package/src/customizations/volto/components/manage/Blocks/Search/layout/RightColumnFacets.jsx +37 -3
  26. package/src/customizations/volto/components/manage/Blocks/Search/schema.js +20 -0
  27. package/src/customizations/volto/components/theme/NotFound/NotFound.jsx +89 -7
  28. package/src/customizations/volto/helpers/Api/APIResourceWithAuth.js +43 -0
  29. package/src/overrideTranslations.jsx +9 -0
  30. package/src/theme/io-sanita/_main.scss +17 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.20.1](https://github.com/RedTurtle/io-sanita-theme/compare/2.20.0...2.20.1) (2025-08-27)
4
+
5
+ ### Bug Fixes
6
+
7
+ * pagination in getTassonomieSearch ([e1b73fe](https://github.com/RedTurtle/io-sanita-theme/commit/e1b73fe25fb106043de69dad6a347b76151b4a8a))
8
+ * scroll page on pagination change in aggregation page ([83e3239](https://github.com/RedTurtle/io-sanita-theme/commit/83e32395fecb5b910d688544d7309194a0e43745))
9
+
10
+ ## [2.20.0](https://github.com/RedTurtle/io-sanita-theme/compare/2.19.0...2.20.0) (2025-08-20)
11
+
12
+ ### Features
13
+
14
+ * search table layout: sort / export csv+pdf ([#102](https://github.com/RedTurtle/io-sanita-theme/issues/102)) ([c0095b8](https://github.com/RedTurtle/io-sanita-theme/commit/c0095b8c416c9b00433d25b69a71080ba2046e22))
15
+ * Site Search bar into page 404 ([#103](https://github.com/RedTurtle/io-sanita-theme/issues/103)) ([b07b3e0](https://github.com/RedTurtle/io-sanita-theme/commit/b07b3e02f085eb0854f938a987465a1b7f9dd9ea))
16
+
17
+ ### Bug Fixes
18
+
19
+ * 404 on mobile ([3525b85](https://github.com/RedTurtle/io-sanita-theme/commit/3525b8520b6827634a2e9bf1b327d44c135827fd))
20
+ * added space before total results number in search results ([#101](https://github.com/RedTurtle/io-sanita-theme/issues/101)) ([ee9f384](https://github.com/RedTurtle/io-sanita-theme/commit/ee9f384439cfcf9598128cd3de1c0225cbf9f634))
21
+ * missing showDownloadActions option ([12aa80a](https://github.com/RedTurtle/io-sanita-theme/commit/12aa80a024bc2a910a8356f6b2774c21efd3c905))
22
+ * use SelectInput from iosanita-theme in Search SelectFacet component, instead of the one from design-react-kit ([#100](https://github.com/RedTurtle/io-sanita-theme/issues/100)) ([7c4b533](https://github.com/RedTurtle/io-sanita-theme/commit/7c4b5331ae09f662e7ed0fd38ea107cf308340c8))
23
+
24
+ ### Maintenance
25
+
26
+ * fix datatable schema ([1b63442](https://github.com/RedTurtle/io-sanita-theme/commit/1b63442cd8d5a18cda8cd7f21a6ce2172e158675))
27
+
3
28
  ## [2.19.0](https://github.com/RedTurtle/io-sanita-theme/compare/2.18.2...2.19.0) (2025-07-08)
4
29
 
5
30
  ### Features
@@ -896,6 +896,11 @@ msgstr ""
896
896
  msgid "Visible only in view mode"
897
897
  msgstr ""
898
898
 
899
+ #. Default: "We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the search below to help you find what you are looking for:"
900
+ #: overrideTranslations
901
+ msgid "We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the search below to help you find what you are looking for:"
902
+ msgstr ""
903
+
899
904
  #. Default: "You are trying to access a protected resource, please {login} first."
900
905
  #: components/Unauthorized/Unauthorized
901
906
  msgid "You are trying to access a protected resource, please {login} first."
@@ -2359,6 +2364,11 @@ msgstr ""
2359
2364
  msgid "open_end"
2360
2365
  msgstr ""
2361
2366
 
2367
+ #. Default: "or you can go to the"
2368
+ #: overrideTranslations
2369
+ msgid "or you can go to the "
2370
+ msgstr ""
2371
+
2362
2372
  #. Default: "Ordina per"
2363
2373
  #: components/Search/Search
2364
2374
  msgid "order_by"
@@ -891,6 +891,11 @@ msgstr "View all"
891
891
  msgid "Visible only in view mode"
892
892
  msgstr ""
893
893
 
894
+ #. Default: "We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the search below to help you find what you are looking for:"
895
+ #: overrideTranslations
896
+ msgid "We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the search below to help you find what you are looking for:"
897
+ msgstr ""
898
+
894
899
  #. Default: "You are trying to access a protected resource, please {login} first."
895
900
  #: components/Unauthorized/Unauthorized
896
901
  msgid "You are trying to access a protected resource, please {login} first."
@@ -2354,6 +2359,11 @@ msgstr "Open link in a new tab"
2354
2359
  msgid "open_end"
2355
2360
  msgstr "This event has an open/variable end date."
2356
2361
 
2362
+ #. Default: "or you can go to the"
2363
+ #: overrideTranslations
2364
+ msgid "or you can go to the "
2365
+ msgstr ""
2366
+
2357
2367
  #. Default: "Ordina per"
2358
2368
  #: components/Search/Search
2359
2369
  msgid "order_by"
@@ -898,6 +898,11 @@ msgstr "Ver todo"
898
898
  msgid "Visible only in view mode"
899
899
  msgstr ""
900
900
 
901
+ #. Default: "We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the search below to help you find what you are looking for:"
902
+ #: overrideTranslations
903
+ msgid "We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the search below to help you find what you are looking for:"
904
+ msgstr ""
905
+
901
906
  #. Default: "You are trying to access a protected resource, please {login} first."
902
907
  #: components/Unauthorized/Unauthorized
903
908
  msgid "You are trying to access a protected resource, please {login} first."
@@ -2361,6 +2366,11 @@ msgstr ""
2361
2366
  msgid "open_end"
2362
2367
  msgstr ""
2363
2368
 
2369
+ #. Default: "or you can go to the"
2370
+ #: overrideTranslations
2371
+ msgid "or you can go to the "
2372
+ msgstr ""
2373
+
2364
2374
  #. Default: "Ordina per"
2365
2375
  #: components/Search/Search
2366
2376
  msgid "order_by"
@@ -898,6 +898,11 @@ msgstr "Voir tout"
898
898
  msgid "Visible only in view mode"
899
899
  msgstr ""
900
900
 
901
+ #. Default: "We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the search below to help you find what you are looking for:"
902
+ #: overrideTranslations
903
+ msgid "We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the search below to help you find what you are looking for:"
904
+ msgstr ""
905
+
901
906
  #. Default: "You are trying to access a protected resource, please {login} first."
902
907
  #: components/Unauthorized/Unauthorized
903
908
  msgid "You are trying to access a protected resource, please {login} first."
@@ -2361,6 +2366,11 @@ msgstr ""
2361
2366
  msgid "open_end"
2362
2367
  msgstr ""
2363
2368
 
2369
+ #. Default: "or you can go to the"
2370
+ #: overrideTranslations
2371
+ msgid "or you can go to the "
2372
+ msgstr ""
2373
+
2364
2374
  #. Default: "Ordina per"
2365
2375
  #: components/Search/Search
2366
2376
  msgid "order_by"
@@ -891,6 +891,11 @@ msgstr "Vedi tutto"
891
891
  msgid "Visible only in view mode"
892
892
  msgstr "Visibile solo in modalità di view"
893
893
 
894
+ #. Default: "We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the search below to help you find what you are looking for:"
895
+ #: overrideTranslations
896
+ msgid "We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the search below to help you find what you are looking for:"
897
+ msgstr "Ci scusiamo per l'inconveniente, la pagina cui stai provando ad accedere non esiste a questo indirizzo. Puoi usare la barra di ricerca qui sotto per trovare quello che stavi cercando:"
898
+
894
899
  #. Default: "You are trying to access a protected resource, please {login} first."
895
900
  #: components/Unauthorized/Unauthorized
896
901
  msgid "You are trying to access a protected resource, please {login} first."
@@ -2354,6 +2359,11 @@ msgstr ""
2354
2359
  msgid "open_end"
2355
2360
  msgstr ""
2356
2361
 
2362
+ #. Default: "or you can go to the"
2363
+ #: overrideTranslations
2364
+ msgid "or you can go to the "
2365
+ msgstr "oppure vai alla "
2366
+
2357
2367
  #. Default: "Ordina per"
2358
2368
  #: components/Search/Search
2359
2369
  msgid "order_by"
package/locales/volto.pot CHANGED
@@ -1,7 +1,7 @@
1
1
  msgid ""
2
2
  msgstr ""
3
3
  "Project-Id-Version: Plone\n"
4
- "POT-Creation-Date: 2025-06-24T13:43:29.010Z\n"
4
+ "POT-Creation-Date: 2025-08-13T12:41:17.004Z\n"
5
5
  "Last-Translator: Plone i18n <plone-i18n@lists.sourceforge.net>\n"
6
6
  "Language-Team: Plone i18n <plone-i18n@lists.sourceforge.net>\n"
7
7
  "Content-Type: text/plain; charset=utf-8\n"
@@ -893,6 +893,11 @@ msgstr ""
893
893
  msgid "Visible only in view mode"
894
894
  msgstr ""
895
895
 
896
+ #. Default: "We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the search below to help you find what you are looking for:"
897
+ #: overrideTranslations
898
+ msgid "We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the search below to help you find what you are looking for:"
899
+ msgstr ""
900
+
896
901
  #. Default: "You are trying to access a protected resource, please {login} first."
897
902
  #: components/Unauthorized/Unauthorized
898
903
  msgid "You are trying to access a protected resource, please {login} first."
@@ -2356,6 +2361,11 @@ msgstr ""
2356
2361
  msgid "open_end"
2357
2362
  msgstr ""
2358
2363
 
2364
+ #. Default: "or you can go to the"
2365
+ #: overrideTranslations
2366
+ msgid "or you can go to the "
2367
+ msgstr ""
2368
+
2359
2369
  #. Default: "Ordina per"
2360
2370
  #: components/Search/Search
2361
2371
  msgid "order_by"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "io-sanita-theme",
3
- "version": "2.19.0",
3
+ "version": "2.20.1",
4
4
  "description": "io-sanita-theme: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "license": "MIT",
@@ -44,7 +44,14 @@ export function getSearchResults(params) {
44
44
  * @function getTassonomieSearch
45
45
  * @returns {Object} Get search filters action.
46
46
  */
47
- export function getTassonomieSearch({ type, id, portalType, order, page }) {
47
+ export function getTassonomieSearch({
48
+ type,
49
+ id,
50
+ portalType,
51
+ order,
52
+ currentPage,
53
+ b_size,
54
+ }) {
48
55
  /*
49
56
  valori possibili.
50
57
  - type: ['a_chi_si_rivolge_tassonomia','parliamo_di']
@@ -52,8 +59,7 @@ export function getTassonomieSearch({ type, id, portalType, order, page }) {
52
59
  - portal_type: se si vogliono i contenuti di quel portal_type per la tassonomia type/value
53
60
  */
54
61
 
55
- const b_size = config.settings.defaultPageSize;
56
- const params = {};
62
+ const _b_size = b_size ?? config.settings.defaultPageSize;
57
63
 
58
64
  return {
59
65
  type: GET_TASSONOMIE_SEARCH,
@@ -66,10 +72,10 @@ export function getTassonomieSearch({ type, id, portalType, order, page }) {
66
72
  ...(portalType && { portal_type: portalType }),
67
73
  ...(order?.sort_on && { sort_on: order.sort_on }),
68
74
  ...(order?.sort_order & { sort_order: order.sort_order }),
69
- ...(page && {
70
- b_start: b_size * (page - 1),
75
+ ...(currentPage && {
76
+ b_start: _b_size * (currentPage - 1),
71
77
  }),
72
- b_size: b_size,
78
+ b_size: _b_size,
73
79
  },
74
80
  },
75
81
  };
@@ -95,12 +95,16 @@ const TableTemplate = (props) => {
95
95
  const widget_props = {
96
96
  behavior: field_properties.behavior,
97
97
  };
98
+ if (field_properties.widget === 'datetime') {
99
+ widget_props.format = 'DD/MM/yyyy HH:MM';
100
+ }
101
+ // per questi campi si è deciso dii non pubblicare ora:minuti
98
102
  switch (c.field) {
99
103
  case 'apertura_bando':
100
104
  case 'chiusura_procedimento_bando':
101
105
  case 'scadenza_domande_bando':
102
106
  case 'scadenza_bando':
103
- widget_props.format = 'DD MMM yyyy';
107
+ widget_props.format = 'DD/MM/yyyy';
104
108
  break;
105
109
  default:
106
110
  break;
@@ -110,7 +114,7 @@ const TableTemplate = (props) => {
110
114
  field_properties.widget === 'datetime' &&
111
115
  item[c.field]?.indexOf('T00:00') > 0
112
116
  ) {
113
- widget_props.format = 'DD MMM yyyy';
117
+ widget_props.format = 'DD/MM/yyyy';
114
118
  }
115
119
  if (field_properties.vocabulary) {
116
120
  widget_props.vocabulary =
@@ -1,3 +1,8 @@
1
+ // XXX: workaround per template search con tabella e fltri con faccette laterali in edit
2
+ // body.cms-ui.has-toolbar.has-sidebar .public-ui .table-template .px-0.container {
3
+ // width: 100% !important;
4
+ // }
5
+
1
6
  .table-template {
2
7
  table.table {
3
8
  th {
@@ -3,7 +3,11 @@ import cx from 'classnames';
3
3
  import { useIntl, defineMessages } from 'react-intl';
4
4
  import { useSelector } from 'react-redux';
5
5
  import { Container, Row, Col, Button } from 'design-react-kit';
6
- import { SearchBar, QuickSearch } from 'io-sanita-theme/components';
6
+ import {
7
+ SearchBar,
8
+ QuickSearch,
9
+ OverlayLoading,
10
+ } from 'io-sanita-theme/components';
7
11
  import { SearchUtils } from 'io-sanita-theme/helpers';
8
12
  import UniversalLink from '@plone/volto/components/manage/UniversalLink/UniversalLink';
9
13
  import './quickSearchBlock.scss';
@@ -19,9 +23,11 @@ const Body = ({ data, id, isEditMode }) => {
19
23
  const intl = useIntl();
20
24
  const [searchableText, setSearchableText] = useState();
21
25
  const subsite = useSelector((state) => state.subsite?.data);
26
+ const [redirectingToResults, setRedirectingToResults] = useState(false);
22
27
 
23
28
  useEffect(() => {
24
29
  if (searchableText?.length > 0) {
30
+ setRedirectingToResults(true);
25
31
  window.location.href =
26
32
  window.location.origin +
27
33
  SearchUtils.getSearchParamsURL({
@@ -86,6 +92,7 @@ const Body = ({ data, id, isEditMode }) => {
86
92
  </div>
87
93
  )}
88
94
  </Container>
95
+ <OverlayLoading loading={redirectingToResults} />
89
96
  </div>
90
97
  );
91
98
  };
@@ -0,0 +1,18 @@
1
+ /* eslint-disable react-hooks/exhaustive-deps */
2
+ import React from 'react';
3
+
4
+ import { Spinner } from 'design-react-kit';
5
+
6
+ import './overlayLoading.scss';
7
+
8
+ const OverlayLoading = ({ loading }) => {
9
+ return loading ? (
10
+ <div className="overlay loading-results">
11
+ <Spinner active />
12
+ </div>
13
+ ) : (
14
+ <></>
15
+ );
16
+ };
17
+
18
+ export default OverlayLoading;
@@ -0,0 +1,12 @@
1
+ .overlay.loading-results {
2
+ width: 100%;
3
+ height: 100%;
4
+ position: fixed;
5
+ z-index: 9999;
6
+ top: 0;
7
+ left: 0;
8
+ background-color: hsl(0deg 0% 100% / 64%);
9
+ display: flex;
10
+ align-items: center;
11
+ justify-content: center;
12
+ }
@@ -3,7 +3,7 @@
3
3
  * @module components/theme/View/AggregationPage
4
4
  */
5
5
 
6
- import React, { useState, useEffect } from 'react';
6
+ import React, { useState, useEffect, createRef } from 'react';
7
7
  import { defineMessages, useIntl } from 'react-intl';
8
8
  import { useDispatch, useSelector } from 'react-redux';
9
9
  import { Redirect } from 'react-router-dom';
@@ -23,6 +23,7 @@ import {
23
23
  Pagination,
24
24
  SortByWidget,
25
25
  } from 'io-sanita-theme/components';
26
+ import { scrollIntoView } from 'io-sanita-theme/helpers';
26
27
 
27
28
  /* STYLE */
28
29
  import './_aggregationPage.scss';
@@ -57,6 +58,7 @@ const AggregationPage = ({ match, route, location }) => {
57
58
  const dispatch = useDispatch();
58
59
  const id = match?.params?.id ?? '';
59
60
  const type = route?.type;
61
+ const resultsRef = createRef();
60
62
 
61
63
  if (id?.length === 0) {
62
64
  return <Redirect to="/" />;
@@ -69,6 +71,7 @@ const AggregationPage = ({ match, route, location }) => {
69
71
  portalType: null, //per filtrare su un tipo di conteneuto specifico (click dal menu laterale)
70
72
  order: { sort_on: 'relevance', sort_order: 'ascending' },
71
73
  currentPage: 1,
74
+ b_size,
72
75
  });
73
76
  const tassonomieSearch = useSelector((state) => state.tassonomieSearch);
74
77
  const result = useSelector((state) => state.tassonomieSearch.result);
@@ -140,6 +143,7 @@ const AggregationPage = ({ match, route, location }) => {
140
143
  }, [searchParams.portalType, searchParams.order]);
141
144
 
142
145
  const onPaginationChange = (e, { activePage }) => {
146
+ scrollIntoView({ ref: resultsRef.current });
143
147
  setSearchParams({ ...searchParams, currentPage: activePage });
144
148
  };
145
149
 
@@ -161,7 +165,10 @@ const AggregationPage = ({ match, route, location }) => {
161
165
  }}
162
166
  />
163
167
 
164
- <div className="row row-column-border border-light row-column-menu-left">
168
+ <div
169
+ className="row row-column-border border-light row-column-menu-left"
170
+ ref={resultsRef}
171
+ >
165
172
  <aside
166
173
  className="col-md-12 col-lg-4"
167
174
  aria-label={intl.formatMessage(commonIntlMessages.sideMenuIndex)}
@@ -1,4 +1,5 @@
1
1
  import loadable from '@loadable/component';
2
+ import OverlayLoading from './OverlayLoading/OverlayLoading';
2
3
 
3
4
  export Icon from 'io-sanita-theme/components/Icon/Icon';
4
5
  export FontAwesomeIcon from 'io-sanita-theme/components/Icon/FontAwesomeIcon';
@@ -30,6 +31,7 @@ export HeaderSlim from 'io-sanita-theme/components/layout/Header/HeaderSlim/Head
30
31
  export HeaderContacts from 'io-sanita-theme/components/layout/Header/HeaderContacts/HeaderContacts';
31
32
  export HeaderCenter from 'io-sanita-theme/components/layout/Header/HeaderCenter';
32
33
  export SubsiteHeader from 'io-sanita-theme/components/layout/Header/SubsiteHeader/SubsiteHeader';
34
+ export OverlayLoading from 'io-sanita-theme/components/OverlayLoading/OverlayLoading';
33
35
  export SearchModal from 'io-sanita-theme/components/layout/Header/HeaderSearch/SearchModal';
34
36
  export UserLoggedMenu from 'io-sanita-theme/components/layout/Header/HeaderSlim/UserLoggedMenu';
35
37
  export LoginButton from 'io-sanita-theme/components/layout/Header/HeaderSlim/LoginButton';
@@ -15,7 +15,12 @@ import {
15
15
  Spinner,
16
16
  } from 'design-react-kit';
17
17
 
18
- import { SearchBar, QuickSearch, Icon } from 'io-sanita-theme/components';
18
+ import {
19
+ SearchBar,
20
+ QuickSearch,
21
+ Icon,
22
+ OverlayLoading,
23
+ } from 'io-sanita-theme/components';
19
24
  import { SearchUtils } from 'io-sanita-theme/helpers';
20
25
 
21
26
  import './searchModal.scss';
@@ -141,11 +146,7 @@ const SearchModal = ({ closeModal, show }) => {
141
146
  />
142
147
  </div>
143
148
  </Container>
144
- {redirectingToResults && (
145
- <div className="overlay loading-results">
146
- <Spinner active />
147
- </div>
148
- )}
149
+ <OverlayLoading loading={redirectingToResults} />
149
150
  </ModalBody>
150
151
  </Modal>
151
152
  );
@@ -43,19 +43,6 @@ body.search-modal-opened {
43
43
  margin-left: auto;
44
44
  }
45
45
 
46
- .overlay.loading-results {
47
- width: 100%;
48
- height: 100%;
49
- position: fixed;
50
- z-index: 9999;
51
- top: 0;
52
- left: 0;
53
- background-color: hsl(0deg 0% 100% / 64%);
54
- display: flex;
55
- align-items: center;
56
- justify-content: center;
57
- }
58
-
59
46
  @media (max-width: #{map-get($grid-breakpoints, lg)}) {
60
47
  .quick-search {
61
48
  h2.h6,
@@ -287,6 +287,8 @@ export const applyIoSanitaBlocksConfig = (config) => {
287
287
  },
288
288
  search: {
289
289
  ...config.blocks.blocksConfig.search,
290
+ // filtro top non personalizzato / non funzionante con layout agid
291
+ variations: config.blocks.blocksConfig.search.variations.filter((v) => v.id !== 'facetsTopSide'),
290
292
  templates: [
291
293
  ...listingVariations.map((v) => v.id).filter((v) => v !== 'carousel'),
292
294
  ],
@@ -4,7 +4,7 @@
4
4
  existing listing template styles
5
5
  */
6
6
 
7
- import React from 'react';
7
+ import React, { useState, useEffect } from 'react';
8
8
 
9
9
  import ListingBody from '@plone/volto/components/manage/Blocks/Listing/ListingBody';
10
10
  import { withBlockExtensions } from '@plone/volto/helpers/Extensions';
@@ -19,6 +19,14 @@ import { compose } from 'redux';
19
19
  import { useSelector } from 'react-redux';
20
20
  import isEqual from 'lodash/isEqual';
21
21
  import isFunction from 'lodash/isFunction';
22
+ import { useIntl, defineMessages } from 'react-intl';
23
+
24
+ const messages = defineMessages({
25
+ downloadInFormat: {
26
+ id: 'downloadInFormat',
27
+ defaultMessage: 'download in formato',
28
+ },
29
+ });
22
30
 
23
31
  const getListingBodyVariation = (data) => {
24
32
  const { variations } = config.blocks.blocksConfig.listing;
@@ -67,8 +75,10 @@ const applyDefaults = (data, root) => {
67
75
  };
68
76
 
69
77
  const SearchBlockView = (props) => {
70
- const { data, searchData, mode = 'view', variation } = props;
78
+ // console.log(props);
79
+ const { data, searchData, mode = 'view', variation, path, id } = props;
71
80
 
81
+ const intl = useIntl();
72
82
  const Layout =
73
83
  variation?.view ||
74
84
  config.blocks.blocksConfig.search.variations.find(
@@ -93,6 +103,22 @@ const SearchBlockView = (props) => {
93
103
 
94
104
  const { variations } = config.blocks.blocksConfig.listing;
95
105
  const listingBodyVariation = variations.find(({ id }) => id === selectedView);
106
+
107
+ const [downloadUrl, setDownloadUrl] = useState('');
108
+
109
+ useEffect(
110
+ () =>
111
+ setDownloadUrl(
112
+ `${path}/searchblock/@@download/${id}.__FORMAT__?${new URLSearchParams({
113
+ ...props.facets,
114
+ search: props.searchText,
115
+ sort_on: props.sortOn,
116
+ sort_order: props.sortOrder,
117
+ })}`,
118
+ ),
119
+ [props.facets, props.sortOn, props.sortOrder, props.searchText],
120
+ );
121
+
96
122
  if (!Layout) return null;
97
123
 
98
124
  return (
@@ -111,6 +137,28 @@ const SearchBlockView = (props) => {
111
137
  path={props.path}
112
138
  isEditMode={mode === 'edit'}
113
139
  />
140
+ {downloadUrl && data?.showDownloadActions && (
141
+ <div class="text-right" style={{textAlign: 'right'}}>
142
+ {intl.formatMessage(messages.downloadInFormat)}:{' '}
143
+ <a
144
+ href={downloadUrl.replace('__FORMAT__', 'csv')}
145
+ download
146
+ className="btn btn-xs btn-primary inline-link"
147
+ disabled={!downloadUrl}
148
+ >
149
+ CSV
150
+ </a>{' '}
151
+ <a
152
+ href={downloadUrl.replace('__FORMAT__', 'pdf')}
153
+ download
154
+ className="btn btn-xs btn-primary inline-link"
155
+ disabled={!downloadUrl}
156
+ >
157
+ PDF
158
+ </a>
159
+ {/* <a href={downloadUrl.replace('__FORMAT__', 'html')} className="btn btn-xs btn-primary inline-link">HTML</a> */}
160
+ </div>
161
+ )}
114
162
  </div>
115
163
  </Layout>
116
164
  </div>
@@ -24,7 +24,7 @@ const SearchDetails = ({ total, text, as = 'p', data }) => {
24
24
  {intl.formatMessage(commonSearchBlockMessages.searchedFor, {
25
25
  em: (...chunks) => <em>{chunks}</em>,
26
26
  searchedtext: text,
27
- })}
27
+ })}{' '}
28
28
  </>
29
29
  )}
30
30
  {data.showTotalResults && (
@@ -7,7 +7,7 @@ import {
7
7
  selectFacetStateToValue,
8
8
  selectFacetValueToQuery,
9
9
  } from '@plone/volto/components/manage/Blocks/Search/components/base';
10
- import { Select } from 'design-react-kit';
10
+ import { SelectInput } from 'io-sanita-theme/components';
11
11
 
12
12
  const SelectFacet = (props) => {
13
13
  const { facet, choices, isMulti, onChange, value, isEditMode } = props;
@@ -23,7 +23,7 @@ const SelectFacet = (props) => {
23
23
  {facet?.title || facet?.field?.label || ''}
24
24
  </label> */}
25
25
  {/* Cannot style with props because the kit is... the kit. Resorting to div[class*='-ValueContainer'] */}
26
- <Select
26
+ <SelectInput
27
27
  placeholder={facet?.title ?? (facet?.field?.label || 'select...')}
28
28
  aria-label={facet?.title ?? (facet?.field?.label || 'select...')}
29
29
  id={facet['@id']}
@@ -0,0 +1,85 @@
1
+ /* CUSTOMIZATIONS:
2
+ - Agid styling
3
+ */
4
+ import { defineMessages, useIntl } from 'react-intl';
5
+ // import upSVG from '@plone/volto/icons/sort-up.svg';
6
+ // import downSVG from '@plone/volto/icons/sort-down.svg';
7
+ // import { Container, Row, Col, Icon } from 'design-react-kit';
8
+ import { SelectInput } from 'io-sanita-theme/components';
9
+
10
+ const messages = defineMessages({
11
+ noSelection: {
12
+ id: 'No selection',
13
+ defaultMessage: 'No selection',
14
+ },
15
+ sortOn: {
16
+ id: 'Sort on',
17
+ defaultMessage: 'Sort on',
18
+ },
19
+ ascending: {
20
+ id: 'Ascending',
21
+ defaultMessage: 'Ascending',
22
+ },
23
+ descending: {
24
+ id: 'Descending',
25
+ defaultMessage: 'Descending',
26
+ },
27
+ sortedOn: {
28
+ id: 'Sorted on',
29
+ defaultMessage: 'Sorted on',
30
+ },
31
+ });
32
+
33
+ const SortOn = (props) => {
34
+ const {
35
+ data = {},
36
+ sortOn = null,
37
+ sortOrder = null,
38
+ setSortOn,
39
+ setSortOrder,
40
+ isEditMode,
41
+ querystring = {},
42
+ } = props;
43
+
44
+ const intl = useIntl();
45
+ const sortableOptions = data?.columns
46
+ ? [
47
+ { value: 'sortable_title', label: 'Titolo' },
48
+ ...data?.columns?.map((f) => {
49
+ return { value: f.field, label: f.title };
50
+ }),
51
+ ].filter((o) => querystring?.['indexes']?.[o.value]?.sortable)
52
+ : [{ value: 'sortable_title', label: 'Titolo' }];
53
+
54
+ const sortOrderOptions = [
55
+ { value: 'ascending', label: intl.formatMessage(messages.ascending) },
56
+ { value: 'descending', label: intl.formatMessage(messages.descending) },
57
+ ];
58
+
59
+ return (
60
+ <div className="pt-4">
61
+ <h6>{intl.formatMessage(messages.sortOn)}</h6>
62
+ {/* <pre>{JSON.stringify(searchData, null,2)}</pre> */}
63
+ {/* <pre>{JSON.stringify(data.columns, null,2)}</pre> */}
64
+ {/* <pre>{sortOn} {sortOrder}</pre> */}
65
+ <div className="pt-2">
66
+ <SelectInput
67
+ id="sortOn"
68
+ value={sortableOptions.find((o) => o.value === sortOn)}
69
+ onChange={(opt) => setSortOn(opt.value)}
70
+ options={sortableOptions}
71
+ />
72
+ </div>
73
+ <div className="pt-2">
74
+ <SelectInput
75
+ id="sortOrder"
76
+ value={sortOrderOptions.find((o) => o.value === sortOrder)}
77
+ onChange={(opt) => setSortOrder(opt.value)}
78
+ options={sortOrderOptions}
79
+ />
80
+ </div>
81
+ </div>
82
+ );
83
+ };
84
+
85
+ export default SortOn;
@@ -7,6 +7,7 @@ import {
7
7
  SearchDetails,
8
8
  Facets,
9
9
  FilterList,
10
+ SortOn,
10
11
  } from '@plone/volto/components/manage/Blocks/Search/components';
11
12
  import UniversalLink from '@plone/volto/components/manage/UniversalLink/UniversalLink';
12
13
  import { Container, Row, Col, Icon } from 'design-react-kit';
@@ -25,6 +26,8 @@ const LeftColumnFacets = (props) => {
25
26
  totalItems,
26
27
  facets,
27
28
  setFacets,
29
+ sortOn,
30
+ sortOrder,
28
31
  onTriggerSearch,
29
32
  searchedText, // search text for previous search
30
33
  isEditMode,
@@ -35,10 +38,10 @@ const LeftColumnFacets = (props) => {
35
38
  } = props;
36
39
  const { showSearchButton } = data;
37
40
  const isLive = !showSearchButton;
38
- const showColumn =
41
+ const showColumn = !isEditMode && (
39
42
  data.columnTextTitle ||
40
43
  richTextHasContent(data.columnText) ||
41
- data?.facets?.length > 0;
44
+ data?.facets?.length > 0);
42
45
  return (
43
46
  <div className="full-width bg-primary-lightest">
44
47
  <Container
@@ -95,6 +98,34 @@ const LeftColumnFacets = (props) => {
95
98
  />
96
99
  </div>
97
100
  )}
101
+ <div className="sort-views-wrapper">
102
+ {data.showSortOn && (
103
+ <SortOn
104
+ data={data}
105
+ querystring={querystring}
106
+ isEditMode={isEditMode}
107
+ sortOrder={sortOrder}
108
+ sortOn={sortOn}
109
+ setSortOn={(sortOn) => {
110
+ flushSync(() => {
111
+ // setSortOn(sortOn);
112
+ onTriggerSearch(searchedText || '', facets, sortOn);
113
+ });
114
+ }}
115
+ setSortOrder={(sortOrder) => {
116
+ flushSync(() => {
117
+ // setSortOrder(sortOrder);
118
+ onTriggerSearch(
119
+ searchedText || '',
120
+ facets,
121
+ sortOn,
122
+ sortOrder,
123
+ );
124
+ });
125
+ }}
126
+ />
127
+ )}
128
+ </div>
98
129
  </div>
99
130
  )}
100
131
 
@@ -1,12 +1,12 @@
1
1
  /* CUSTOMIZATIONS:
2
2
  - Agid styling
3
3
  */
4
- import React from 'react';
5
4
  import {
6
5
  SearchInput,
7
6
  SearchDetails,
8
7
  Facets,
9
8
  FilterList,
9
+ SortOn,
10
10
  } from '@plone/volto/components/manage/Blocks/Search/components';
11
11
  import UniversalLink from '@plone/volto/components/manage/UniversalLink/UniversalLink';
12
12
  import { Container, Row, Col, Icon } from 'design-react-kit';
@@ -26,6 +26,8 @@ const RightColumnFacets = (props) => {
26
26
  totalItems,
27
27
  facets,
28
28
  setFacets,
29
+ sortOn,
30
+ sortOrder,
29
31
  onTriggerSearch,
30
32
  searchedText, // search text for previous search
31
33
  isEditMode,
@@ -36,10 +38,13 @@ const RightColumnFacets = (props) => {
36
38
  } = props;
37
39
  const { showSearchButton } = data;
38
40
  const isLive = !showSearchButton;
39
- const showColumn =
41
+
42
+ const showColumn = !isEditMode && (
40
43
  data.columnTextTitle ||
41
44
  richTextHasContent(data.columnText) ||
42
- data?.facets?.length > 0;
45
+ data?.facets?.length > 0 ||
46
+ data?.showOrderOptions);
47
+
43
48
  return (
44
49
  <div className="full-width bg-primary-lightest">
45
50
  <Container className="searchBlock-facets right-column-facets" stackable>
@@ -125,6 +130,35 @@ const RightColumnFacets = (props) => {
125
130
  />
126
131
  </div>
127
132
  )}
133
+
134
+ <div className="sort-views-wrapper">
135
+ {data.showSortOn && (
136
+ <SortOn
137
+ data={data}
138
+ querystring={querystring}
139
+ isEditMode={isEditMode}
140
+ sortOrder={sortOrder}
141
+ sortOn={sortOn}
142
+ setSortOn={(sortOn) => {
143
+ flushSync(() => {
144
+ // setSortOn(sortOn);
145
+ onTriggerSearch(searchedText || '', facets, sortOn);
146
+ });
147
+ }}
148
+ setSortOrder={(sortOrder) => {
149
+ flushSync(() => {
150
+ // setSortOrder(sortOrder);
151
+ onTriggerSearch(
152
+ searchedText || '',
153
+ facets,
154
+ sortOn,
155
+ sortOrder,
156
+ );
157
+ });
158
+ }}
159
+ />
160
+ )}
161
+ </div>
128
162
  </div>
129
163
  )}
130
164
  </Row>
@@ -123,6 +123,14 @@ const messages = defineMessages({
123
123
  id: 'Show total results',
124
124
  defaultMessage: 'Show total results',
125
125
  },
126
+ showSortOn: {
127
+ id: 'Show sorting?',
128
+ defaultMessage: 'Show sorting?',
129
+ },
130
+ showDownloadActions: {
131
+ id: 'showDownloadActions',
132
+ defaultMessage: 'Mostra azioni downlad CSV/PDF',
133
+ },
126
134
  columnTextTitle: {
127
135
  id: 'columnTextTitle',
128
136
  defaultMessage: 'Intestazione della colonna',
@@ -282,6 +290,8 @@ const SearchSchema = ({ data = {}, intl }) => {
282
290
  // ...(data.showSearchInput ? ['searchInputPrompt'] : []),
283
291
  // ...(data.showSearchButton ? ['searchButtonLabel'] : []),
284
292
  'showTotalResults',
293
+ 'showSortOn',
294
+ 'showDownloadActions',
285
295
  ],
286
296
  },
287
297
  ],
@@ -307,6 +317,16 @@ const SearchSchema = ({ data = {}, intl }) => {
307
317
  title: intl.formatMessage(messages.showTotalResults),
308
318
  default: true,
309
319
  },
320
+ showSortOn: {
321
+ type: 'boolean',
322
+ title: intl.formatMessage(messages.showSortOn),
323
+ default: false,
324
+ },
325
+ showDownloadActions: {
326
+ type: 'boolean',
327
+ title: intl.formatMessage(messages.showDownloadActions),
328
+ default: true,
329
+ },
310
330
  searchButtonLabel: {
311
331
  title: intl.formatMessage(messages.searchButtonLabel),
312
332
  placeholder: intl.formatMessage(messages.searchButtonPlaceholder),
@@ -1,12 +1,16 @@
1
1
  /*
2
2
  CUSTOMIZATIONS:
3
3
  - Removed the "Site Administration" link, added a link to the home page
4
+ - Added a Search in site bar
4
5
  */
5
6
 
6
- import { useEffect } from 'react';
7
+ import { useEffect, useState, useRef } from 'react';
7
8
 
8
9
  import BodyClass from '@plone/volto/helpers/BodyClass/BodyClass';
9
10
  import { FormattedMessage } from 'react-intl';
11
+ import { defineMessages, useIntl } from 'react-intl';
12
+ import { useLocation } from 'react-router-dom';
13
+ import qs from 'query-string';
10
14
  import { Link } from 'react-router-dom';
11
15
  import { Container } from 'semantic-ui-react';
12
16
  import {
@@ -16,15 +20,55 @@ import {
16
20
  import { useDispatch, useSelector } from 'react-redux';
17
21
  import { getNavigation } from '@plone/volto/actions/navigation/navigation';
18
22
  import config from '@plone/volto/registry';
23
+ import { SearchBar, OverlayLoading } from 'io-sanita-theme/components';
24
+ import { SearchUtils } from 'io-sanita-theme/helpers';
25
+
26
+ import { Spinner } from 'design-react-kit';
19
27
 
20
28
  /**
21
29
  * Not found function.
22
30
  * @function NotFound
23
31
  * @returns {string} Markup of the not found page.
24
32
  */
33
+
34
+ const { getSearchParamsURL } = SearchUtils;
35
+
36
+ const messages = defineMessages({
37
+ closeSearch: {
38
+ id: 'closeSearch',
39
+ defaultMessage: 'Chiudi cerca',
40
+ },
41
+ closeSearchBack: {
42
+ id: 'closeSearchBack',
43
+ defaultMessage: 'Indietro',
44
+ },
45
+ search: {
46
+ id: 'search',
47
+ defaultMessage: 'Cerca',
48
+ },
49
+ searchLabel: {
50
+ id: 'searchLabel',
51
+ defaultMessage: 'Cerca nel sito',
52
+ },
53
+ error404maintext: {
54
+ id: 'We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the search below to help you find what you are looking for:',
55
+ defaultMessage:
56
+ 'We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the search below to help you find what you are looking for:',
57
+ },
58
+ error404hplink: {
59
+ id: 'or you can go to the ',
60
+ defaultMessage: 'or you can go to the ',
61
+ },
62
+ });
63
+
25
64
  const NotFound = () => {
26
65
  const dispatch = useDispatch();
27
66
  const lang = useSelector((state) => state.intl.locale);
67
+ const intl = useIntl();
68
+ const location = useLocation();
69
+ const inputRef = useRef(null);
70
+ const [redirectingToResults, setRedirectingToResults] = useState(false);
71
+ const subsite = useSelector((state) => state.subsite?.data);
28
72
 
29
73
  const navigationRootPath = config.settings.isMultilingual
30
74
  ? `/${toBackendLang(lang)}`
@@ -34,8 +78,25 @@ const NotFound = () => {
34
78
  dispatch(getNavigation(navigationRootPath, config.settings.navDepth));
35
79
  }, [dispatch, lang, navigationRootPath]);
36
80
 
81
+ const [searchableText, setSearchableText] = useState(
82
+ qs.parse(location.search)?.SearchableText ?? '',
83
+ );
84
+
85
+ const submitSearch = (_searchableText) => {
86
+ if (__CLIENT__) {
87
+ setRedirectingToResults(true);
88
+ window.location.href =
89
+ window.location.origin +
90
+ getSearchParamsURL({
91
+ searchableText: _searchableText ?? searchableText,
92
+ subsite,
93
+ currentLang: intl.locale,
94
+ });
95
+ }
96
+ };
97
+
37
98
  return (
38
- <Container className="view-wrapper">
99
+ <Container className="view-wrapper px-5 text-center py-3">
39
100
  <BodyClass className="page-not-found" />
40
101
  <h1>
41
102
  <FormattedMessage
@@ -43,16 +104,37 @@ const NotFound = () => {
43
104
  defaultMessage="This page does not seem to exist…"
44
105
  />
45
106
  </h1>
46
- <p className="description">
47
- <FormattedMessage
48
- id="We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the links below to help you find what you are looking for."
49
- defaultMessage="We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the links below to help you find what you are looking for."
50
- />
107
+ <p className="description text-center mt-3">
108
+ {intl.formatMessage(messages.error404maintext)}
51
109
  </p>
110
+ <Container className="search-bar-container my-5">
111
+ <div
112
+ className="search-filters"
113
+ role="search"
114
+ aria-label={intl.formatMessage(messages.searchLabel)}
115
+ >
116
+ <div className="mb-4">
117
+ <SearchBar
118
+ id="search-site-modal"
119
+ value={searchableText}
120
+ onChange={(v) => {
121
+ setSearchableText(v);
122
+ submitSearch(v);
123
+ }}
124
+ showSubmit={true}
125
+ ref={inputRef}
126
+ />
127
+ </div>
128
+ </div>
129
+ <OverlayLoading loading={redirectingToResults} />
130
+ </Container>
131
+
52
132
  <p>
133
+ {intl.formatMessage(messages.error404hplink)}
53
134
  <Link to={navigationRootPath}>
54
135
  <FormattedMessage id="Home page" defaultMessage="Home page" />
55
136
  </Link>
137
+ .
56
138
  </p>
57
139
  {/* <p>
58
140
  <FormattedMessage
@@ -0,0 +1,43 @@
1
+ /**
2
+ CUSTOMIZATIONS:
3
+ * Original from @plone/volto 18.9.1
4
+ *
5
+ * - querystring parameters are passed to the backend
6
+ *
7
+ */
8
+
9
+ import superagent from 'superagent';
10
+ import config from '@plone/volto/registry';
11
+ import { addHeadersFactory } from '@plone/volto/helpers/Proxy/Proxy';
12
+
13
+ /**
14
+ * Get a resource image/file with authenticated (if token exist) API headers
15
+ * @function getAPIResourceWithAuth
16
+ * @param {Object} req Request object
17
+ * @return {string} The response with the image
18
+ */
19
+ export const getAPIResourceWithAuth = (req) =>
20
+ new Promise((resolve, reject) => {
21
+ const { settings } = config;
22
+ const APISUFIX = settings.legacyTraverse ? '' : '/++api++';
23
+
24
+ let apiPath = '';
25
+ if (settings.internalApiPath && __SERVER__) {
26
+ apiPath = settings.internalApiPath;
27
+ } else if (__DEVELOPMENT__ && settings.devProxyToApiPath) {
28
+ apiPath = settings.devProxyToApiPath;
29
+ } else {
30
+ apiPath = settings.apiPath;
31
+ }
32
+ const request = superagent
33
+ .get(`${apiPath}${__DEVELOPMENT__ ? '' : APISUFIX}${req.path}`)
34
+ .query(req.query)
35
+ .maxResponseSize(settings.maxResponseSize)
36
+ .responseType('blob');
37
+ const authToken = req.universalCookies.get('auth_token');
38
+ if (authToken) {
39
+ request.set('Authorization', `Bearer ${authToken}`);
40
+ }
41
+ request.use(addHeadersFactory(req));
42
+ request.then(resolve).catch(reject);
43
+ });
@@ -178,4 +178,13 @@ defineMessages({
178
178
  id: 'descendingTableSort',
179
179
  defaultMessage: 'descending',
180
180
  },
181
+ error404maintext: {
182
+ id: 'We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the search below to help you find what you are looking for:',
183
+ defaultMessage:
184
+ 'We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the search below to help you find what you are looking for:',
185
+ },
186
+ error404hplink: {
187
+ id: 'or you can go to the ',
188
+ defaultMessage: 'or you can go to the ',
189
+ },
181
190
  });
@@ -301,3 +301,20 @@ picture.volto-image.responsive img.full-width,
301
301
  margin: 0;
302
302
  box-shadow: none;
303
303
  }
304
+
305
+ //error 404
306
+ body.page-not-found {
307
+ .search-bar-container {
308
+ position: relative;
309
+ }
310
+
311
+ #customer-satisfaction-form {
312
+ display: none;
313
+ }
314
+
315
+ @media (min-width: #{map-get($grid-breakpoints, lg)}) {
316
+ .view-wrapper {
317
+ max-width: 50vw;
318
+ }
319
+ }
320
+ }