From 98728a3440ad18bf0bf86c544239dcb9541c0557 Mon Sep 17 00:00:00 2001 From: Piero Nicolli Date: Mon, 15 Jan 2024 17:06:54 +0100 Subject: [PATCH] feat: add new widget for event luoghi_correlati with custom form validation --- RELEASE.md | 6 + locales/de/LC_MESSAGES/volto.po | 5 + locales/en/LC_MESSAGES/volto.po | 5 + locales/es/LC_MESSAGES/volto.po | 5 + locales/fr/LC_MESSAGES/volto.po | 5 + locales/it/LC_MESSAGES/volto.po | 5 + locales/volto.pot | 7 +- .../Widgets/LuoghiCorrelatiEventoWidget.jsx | 64 ++++ src/config/Widgets/widgets.js | 2 + .../manage/Widgets/SelectWidget.jsx | 314 ++++++++++++++++++ .../FormValidation/FormValidationHelpers.js | 98 ++++++ .../Widgets/_luoghiCorrelatiEventoWidget.scss | 15 + src/theme/site.scss | 1 + 13 files changed, 531 insertions(+), 1 deletion(-) create mode 100644 src/components/ItaliaTheme/manage/Widgets/LuoghiCorrelatiEventoWidget.jsx create mode 100644 src/customizations/volto/components/manage/Widgets/SelectWidget.jsx create mode 100644 src/theme/ItaliaTheme/Widgets/_luoghiCorrelatiEventoWidget.scss diff --git a/RELEASE.md b/RELEASE.md index 8b040db3f..5ceecb331 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -41,6 +41,12 @@ - ... --> +## Versione X.X.X (dd/mm/yyyy) + +### Novità + +- Ora per gli eventi è obbligatorio inserire il campo "Luoghi correlati" oppure compilare i campi dell'indirizzo. + ## Versione 11.2.1 (15/01/2024) ### Migliorie diff --git a/locales/de/LC_MESSAGES/volto.po b/locales/de/LC_MESSAGES/volto.po index d13c939ee..4c4441876 100644 --- a/locales/de/LC_MESSAGES/volto.po +++ b/locales/de/LC_MESSAGES/volto.po @@ -2420,6 +2420,11 @@ msgstr "" msgid "luoghi_correlati" msgstr "" +#: components/ItaliaTheme/manage/Widgets/LuoghiCorrelatiEventoWidget +# defaultMessage: Luogo dell'evento +msgid "luoghi_correlati_evento_widget_title" +msgstr "" + #: components/ItaliaTheme/View/NewsItemView/NewsItemLuoghiCorrelati # defaultMessage: Luoghi msgid "luoghi_notizia" diff --git a/locales/en/LC_MESSAGES/volto.po b/locales/en/LC_MESSAGES/volto.po index 41744730c..9b81f08c0 100644 --- a/locales/en/LC_MESSAGES/volto.po +++ b/locales/en/LC_MESSAGES/volto.po @@ -2405,6 +2405,11 @@ msgstr "" msgid "luoghi_correlati" msgstr "" +#: components/ItaliaTheme/manage/Widgets/LuoghiCorrelatiEventoWidget +# defaultMessage: Luogo dell'evento +msgid "luoghi_correlati_evento_widget_title" +msgstr "Event location" + #: components/ItaliaTheme/View/NewsItemView/NewsItemLuoghiCorrelati # defaultMessage: Luoghi msgid "luoghi_notizia" diff --git a/locales/es/LC_MESSAGES/volto.po b/locales/es/LC_MESSAGES/volto.po index bcb961036..14b8a4851 100644 --- a/locales/es/LC_MESSAGES/volto.po +++ b/locales/es/LC_MESSAGES/volto.po @@ -2414,6 +2414,11 @@ msgstr "" msgid "luoghi_correlati" msgstr "" +#: components/ItaliaTheme/manage/Widgets/LuoghiCorrelatiEventoWidget +# defaultMessage: Luogo dell'evento +msgid "luoghi_correlati_evento_widget_title" +msgstr "" + #: components/ItaliaTheme/View/NewsItemView/NewsItemLuoghiCorrelati # defaultMessage: Luoghi msgid "luoghi_notizia" diff --git a/locales/fr/LC_MESSAGES/volto.po b/locales/fr/LC_MESSAGES/volto.po index 91079293f..cf8cbff7b 100644 --- a/locales/fr/LC_MESSAGES/volto.po +++ b/locales/fr/LC_MESSAGES/volto.po @@ -2422,6 +2422,11 @@ msgstr "" msgid "luoghi_correlati" msgstr "" +#: components/ItaliaTheme/manage/Widgets/LuoghiCorrelatiEventoWidget +# defaultMessage: Luogo dell'evento +msgid "luoghi_correlati_evento_widget_title" +msgstr "" + #: components/ItaliaTheme/View/NewsItemView/NewsItemLuoghiCorrelati # defaultMessage: Luoghi msgid "luoghi_notizia" diff --git a/locales/it/LC_MESSAGES/volto.po b/locales/it/LC_MESSAGES/volto.po index d04828644..1227bc15a 100644 --- a/locales/it/LC_MESSAGES/volto.po +++ b/locales/it/LC_MESSAGES/volto.po @@ -2405,6 +2405,11 @@ msgstr "Logo" msgid "luoghi_correlati" msgstr "Luoghi correlati" +#: components/ItaliaTheme/manage/Widgets/LuoghiCorrelatiEventoWidget +# defaultMessage: Luogo dell'evento +msgid "luoghi_correlati_evento_widget_title" +msgstr "Luogo dell'evento" + #: components/ItaliaTheme/View/NewsItemView/NewsItemLuoghiCorrelati # defaultMessage: Luoghi msgid "luoghi_notizia" diff --git a/locales/volto.pot b/locales/volto.pot index 33661a130..448faf6fd 100644 --- a/locales/volto.pot +++ b/locales/volto.pot @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Plone\n" -"POT-Creation-Date: 2024-01-11T13:44:40.490Z\n" +"POT-Creation-Date: 2024-01-15T16:05:18.583Z\n" "Last-Translator: Plone i18n \n" "Language-Team: Plone i18n \n" "MIME-Version: 1.0\n" @@ -2407,6 +2407,11 @@ msgstr "" msgid "luoghi_correlati" msgstr "" +#: components/ItaliaTheme/manage/Widgets/LuoghiCorrelatiEventoWidget +# defaultMessage: Luogo dell'evento +msgid "luoghi_correlati_evento_widget_title" +msgstr "" + #: components/ItaliaTheme/View/NewsItemView/NewsItemLuoghiCorrelati # defaultMessage: Luoghi msgid "luoghi_notizia" diff --git a/src/components/ItaliaTheme/manage/Widgets/LuoghiCorrelatiEventoWidget.jsx b/src/components/ItaliaTheme/manage/Widgets/LuoghiCorrelatiEventoWidget.jsx new file mode 100644 index 000000000..a7a1cf06d --- /dev/null +++ b/src/components/ItaliaTheme/manage/Widgets/LuoghiCorrelatiEventoWidget.jsx @@ -0,0 +1,64 @@ +import { defineMessages, useIntl } from 'react-intl'; +import { Form } from 'semantic-ui-react'; +import cx from 'classnames'; +import config from '@plone/volto/registry'; + +const messages = defineMessages({ + luoghi_correlati_evento_widget_title: { + id: 'luoghi_correlati_evento_widget_title', + defaultMessage: "Luogo dell'evento", + }, +}); + +const LuoghiCorrelatiEventoWidget = (props) => { + const Widget = config.widgets.factory['Relation List']; + const intl = useIntl(); + + return ( + +
+

+ {intl.formatMessage(messages.luoghi_correlati_evento_widget_title)} +

+

+ Sezione obbligatoria secondo il{' '} + + documento di buone pratiche per l'asseverazione dei siti + + . Per confermare la presenza in pagina della relativa sezione devono + essere presenti: +

    +
  • + «Luoghi correlati» che è un campo con un riferimento ad un oggetto + di tipo Luogo già presente sul sito. Se compilato, questo campo è + sufficiente per la validazione della compilazione del modulo. +
  • +
  • + Se non è possibile compilare il campo «Luoghi correlati», è + necessario compilare almeno i campi «Nome sede», «Via», «Città» e + «Nazione» sottostanti. +
  • +
+

+
+ +
+ ); +}; + +export default LuoghiCorrelatiEventoWidget; diff --git a/src/config/Widgets/widgets.js b/src/config/Widgets/widgets.js index 43bdcc925..2cd3603d7 100644 --- a/src/config/Widgets/widgets.js +++ b/src/config/Widgets/widgets.js @@ -17,6 +17,7 @@ import { LocationFiltersWidget, CanaleDigitaleWidget, } from 'design-comuni-plone-theme/components/ItaliaTheme'; +import LuoghiCorrelatiEventoWidget from 'design-comuni-plone-theme/components/ItaliaTheme/manage/Widgets/LuoghiCorrelatiEventoWidget'; const getItaliaWidgets = (config) => { config.registerComponent({ @@ -86,6 +87,7 @@ const getItaliaWidgets = (config) => { color_list: ColorListWidget, path_filters: PathFiltersWidget, location_filter: LocationFiltersWidget, + luoghi_correlati_evento: LuoghiCorrelatiEventoWidget, }, }; }; diff --git a/src/customizations/volto/components/manage/Widgets/SelectWidget.jsx b/src/customizations/volto/components/manage/Widgets/SelectWidget.jsx new file mode 100644 index 000000000..87f7a27ed --- /dev/null +++ b/src/customizations/volto/components/manage/Widgets/SelectWidget.jsx @@ -0,0 +1,314 @@ +/** + * SelectWidget component. + * @module components/manage/Widgets/SelectWidget + * Customizations: + * - pass props.onBlur to Select component + */ + +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { compose } from 'redux'; +import { map } from 'lodash'; +import { defineMessages, injectIntl } from 'react-intl'; +import { + getVocabFromHint, + getVocabFromField, + getVocabFromItems, +} from '@plone/volto/helpers'; +import { FormFieldWrapper } from '@plone/volto/components'; +import { getVocabulary, getVocabularyTokenTitle } from '@plone/volto/actions'; +import { normalizeValue } from '@plone/volto/components/manage/Widgets/SelectUtils'; + +import { + customSelectStyles, + DropdownIndicator, + ClearIndicator, + Option, + selectTheme, + MenuList, + MultiValueContainer, +} from '@plone/volto/components/manage/Widgets/SelectStyling'; +import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; + +const messages = defineMessages({ + default: { + id: 'Default', + defaultMessage: 'Default', + }, + idTitle: { + id: 'Short Name', + defaultMessage: 'Short Name', + }, + idDescription: { + id: 'Used for programmatic access to the fieldset.', + defaultMessage: 'Used for programmatic access to the fieldset.', + }, + title: { + id: 'Title', + defaultMessage: 'Title', + }, + description: { + id: 'Description', + defaultMessage: 'Description', + }, + close: { + id: 'Close', + defaultMessage: 'Close', + }, + choices: { + id: 'Choices', + defaultMessage: 'Choices', + }, + required: { + id: 'Required', + defaultMessage: 'Required', + }, + select: { + id: 'Select…', + defaultMessage: 'Select…', + }, + no_value: { + id: 'No value', + defaultMessage: 'No value', + }, + no_options: { + id: 'No options', + defaultMessage: 'No options', + }, +}); + +/** + * SelectWidget component class. + * @function SelectWidget + * @returns {string} Markup of the component. + */ +class SelectWidget extends Component { + /** + * Property types. + * @property {Object} propTypes Property types. + * @static + */ + static propTypes = { + id: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + description: PropTypes.string, + required: PropTypes.bool, + error: PropTypes.arrayOf(PropTypes.string), + getVocabulary: PropTypes.func.isRequired, + getVocabularyTokenTitle: PropTypes.func.isRequired, + choices: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.object, PropTypes.array]), + ), + items: PropTypes.shape({ + vocabulary: PropTypes.object, + }), + widgetOptions: PropTypes.shape({ + vocabulary: PropTypes.object, + }), + value: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.string, + PropTypes.bool, + PropTypes.func, + PropTypes.array, + ]), + onChange: PropTypes.func.isRequired, + onBlur: PropTypes.func, + onClick: PropTypes.func, + onEdit: PropTypes.func, + onDelete: PropTypes.func, + wrapped: PropTypes.bool, + noValueOption: PropTypes.bool, + customOptionStyling: PropTypes.any, + isMulti: PropTypes.bool, + placeholder: PropTypes.string, + }; + + /** + * Default properties + * @property {Object} defaultProps Default properties. + * @static + */ + static defaultProps = { + description: null, + required: false, + items: { + vocabulary: null, + }, + widgetOptions: { + vocabulary: null, + }, + error: [], + choices: [], + value: null, + onChange: () => {}, + onBlur: () => {}, + onClick: () => {}, + onEdit: null, + onDelete: null, + noValueOption: true, + customOptionStyling: null, + }; + + /** + * Component did mount + * @method componentDidMount + * @returns {undefined} + */ + componentDidMount() { + if ( + (!this.props.choices || this.props.choices?.length === 0) && + this.props.vocabBaseUrl + ) { + this.props.getVocabulary({ + vocabNameOrURL: this.props.vocabBaseUrl, + size: -1, + subrequest: this.props.lang, + }); + } + } + + componentDidUpdate(prevProps) { + if ( + this.props.vocabBaseUrl !== prevProps.vocabBaseUrl && + (!this.props.choices || this.props.choices?.length === 0) + ) { + this.props.getVocabulary({ + vocabNameOrURL: this.props.vocabBaseUrl, + size: -1, + subrequest: this.props.lang, + }); + } + } + + /** + * Render method. + * @method render + * @returns {string} Markup for the component. + */ + render() { + const { id, choices, value, intl, onChange, onBlur } = this.props; + const normalizedValue = normalizeValue(choices, value, intl); + // Make sure that both disabled and isDisabled (from the DX layout feat work) + const disabled = this.props.disabled || this.props.isDisabled; + const Select = this.props.reactSelect.default; + + let options = this.props.vocabBaseUrl + ? this.props.choices + : [ + ...map(choices, (option) => ({ + value: option[0], + label: + // Fix "None" on the serializer, to remove when fixed in p.restapi + option[1] !== 'None' && option[1] ? option[1] : option[0], + })), + // Only set "no-value" option if there's no default in the field + // TODO: also if this.props.defaultValue? + ...(this.props.noValueOption && + (this.props.default === undefined || this.props.default === null) + ? [ + { + label: this.props.intl.formatMessage(messages.no_value), + value: 'no-value', + }, + ] + : []), + ]; + + const isMulti = this.props.isMulti + ? this.props.isMulti + : id === 'roles' || id === 'groups' || this.props.type === 'array'; + + return ( + +