diff --git a/src/components/forms/event-category-form.js b/src/components/forms/event-category-form.js index 8bde330d3..6a123b04e 100644 --- a/src/components/forms/event-category-form.js +++ b/src/components/forms/event-category-form.js @@ -11,434 +11,478 @@ * limitations under the License. * */ -import React from "react"; +import React, { useEffect, useState } from "react"; +import { useFormik, FormikProvider } from "formik"; +import * as yup from "yup"; import T from "i18n-react/dist/i18n-react"; -import "awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css"; -import Input from "openstack-uicore-foundation/lib/components/inputs/text-input" -import Panel from "openstack-uicore-foundation/lib/components/sections/panel" -import TagInput from "openstack-uicore-foundation/lib/components/inputs/tag-input" -import UploadInput from "openstack-uicore-foundation/lib/components/inputs/upload-input" -import AccessLevelsInput from "openstack-uicore-foundation/lib/components/inputs/access-levels-input" -import SortableTable from "openstack-uicore-foundation/lib/components/table-sortable"; -import TextEditorV3 from "openstack-uicore-foundation/lib/components/inputs/editor-input-v3"; import { - isEmpty, - scrollToError, - shallowEqual, - hasErrors -} from "../../utils/methods"; -import TrackDropdown from "../inputs/track-dropdown"; - -class EventCategoryForm extends React.Component { - constructor(props) { - super(props); + Accordion, + AccordionDetails, + AccordionSummary, + Autocomplete, + Box, + Button, + Grid2, + InputLabel, + TextField, + Tooltip, + Typography +} from "@mui/material"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; +import { UploadInputV3 } from "openstack-uicore-foundation/lib/components"; +import { useSnackbarMessage } from "openstack-uicore-foundation/lib/components/mui/snackbar-notification"; +import MuiTableSortable from "openstack-uicore-foundation/lib/components/mui/sortable-table"; +import { + queryTags, + queryAccessLevels +} from "openstack-uicore-foundation/lib/utils/query-actions"; +import MuiFormikTextField from "openstack-uicore-foundation/lib/components/mui/formik-inputs/textfield"; +import MuiFormikCheckbox from "openstack-uicore-foundation/lib/components/mui/formik-inputs/checkbox"; +import FormikTextEditor from "openstack-uicore-foundation/lib/components/mui/formik-inputs/texteditor"; +import MuiFormikAsyncAutocomplete from "openstack-uicore-foundation/lib/components/mui/formik-inputs/async-select"; +import MuiFormikColorField from "../mui/formik-inputs/mui-formik-color-field"; +import useScrollToError from "../../hooks/useScrollToError"; +import { + requiredStringValidation, + requiredHTMLValidation, + hexColorValidation +} from "../../utils/yup"; - this.state = { - entity: { ...props.entity }, - errors: props.errors, - showSection: "main", - subtrack: null - }; +const validationSchema = yup.object().shape({ + name: requiredStringValidation(), + code: requiredStringValidation(), + color: hexColorValidation().required(T.translate("validation.required")), + text_color: hexColorValidation().required(T.translate("validation.required")), + description: requiredHTMLValidation() +}); - this.handleChange = this.handleChange.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); - this.handleUploadPic = this.handleUploadPic.bind(this); - this.handleRemovePic = this.handleRemovePic.bind(this); - this.handleEditSubCategory = this.handleEditSubCategory.bind(this); - this.handleLinkSubCategory = this.handleLinkSubCategory.bind(this); - this.handleUnlinkSubCategory = this.handleUnlinkSubCategory.bind(this); - this.handleUpdateSubCategoryOrder = - this.handleUpdateSubCategoryOrder.bind(this); +const subtrackColumns = [ + { columnKey: "id", header: T.translate("general.id") }, + { columnKey: "name", header: T.translate("event_category_list.name") }, + { columnKey: "code", header: T.translate("event_category_list.code") }, + { + columnKey: "color", + header: T.translate("event_category_list.color"), + render: (row) => ( + + ) } +]; - componentDidUpdate(prevProps) { - const state = {}; - scrollToError(this.props.errors); +const EventCategoryForm = ({ + currentSummit, + entity, + errors, + history, + onSubmit, + onRemoveImage, + onLinkSubCategory, + onUnlinkSubCategory, + onUpdateSubCategoryOrder +}) => { + const { errorMessage } = useSnackbarMessage(); + const [isSaving, setIsSaving] = useState(false); + const [subtrackToLink, setSubtrackToLink] = useState(null); + const [iconUrl, setIconUrl] = useState(entity.icon_url); + const [scheduleSettingsExpanded, setScheduleSettingsExpanded] = + useState(false); - if (!shallowEqual(prevProps.entity, this.props.entity)) { - state.entity = { ...this.props.entity }; - state.errors = {}; - } + useEffect(() => { + setIconUrl(entity.icon_url); + }, [entity.icon_url]); - if (!shallowEqual(prevProps.errors, this.props.errors)) { - state.errors = { ...this.props.errors }; - } + const handleSubmit = (values) => { + setIsSaving(true); + const normalizedValues = { + ...values, + allowed_tags: values.allowed_tags.map((t) => ({ + id: parseInt(t.value, 10), + tag: t.label + })), + allowed_access_levels: values.allowed_access_levels.map((al) => ({ + id: parseInt(al.value, 10), + name: al.label + })) + }; + Promise.resolve(onSubmit(normalizedValues)).finally(() => + setIsSaving(false) + ); + }; - if (!isEmpty(state)) { - this.setState({ ...this.state, ...state }); - } - } + const formik = useFormik({ + initialValues: { + id: entity.id, + name: entity.name, + code: entity.code, + color: entity.color, + text_color: entity.text_color, + description: entity.description, + session_count: entity.session_count, + alternate_count: entity.alternate_count, + lightning_count: entity.lightning_count, + lightning_alternate_count: entity.lightning_alternate_count, + voting_visible: entity.voting_visible, + chair_visible: entity.chair_visible, + allowed_tags: (entity.allowed_tags || []).map((t) => ({ + value: String(t.id), + label: t.tag + })), + allowed_access_levels: (entity.allowed_access_levels || []).map((al) => ({ + value: String(al.id), + label: al.name + })), + proposed_schedule_transition_time: + entity.proposed_schedule_transition_time ?? "" + }, + validationSchema, + enableReinitialize: true, + onSubmit: handleSubmit + }); - handleChange(ev) { - const entity = { ...this.state.entity }; - const errors = { ...this.state.errors }; - let { value, id } = ev.target; + useScrollToError(formik); - if (ev.target.type === "checkbox") { - value = ev.target.checked; + useEffect(() => { + if (errors && Object.keys(errors).length > 0) { + formik.setErrors(errors); + formik.setTouched( + Object.keys(errors).reduce((acc, key) => ({ ...acc, [key]: true }), {}) + ); } + }, [errors]); - errors[id] = ""; - entity[id] = value; - this.setState({ entity, errors }); - } + const handleIconUploadComplete = (response) => { + setIconUrl(response.url ?? response.file_url ?? response.path ?? null); + }; - handleSubmit(ev) { - ev.preventDefault(); - this.props.onSubmit(this.state.entity); - } + const handleIconUploadError = () => { + errorMessage(T.translate("edit_event_category.icon_upload_error")); + }; - handleRemovePic() { - this.props.onRemoveImage(this.state.entity.id); - } + const handleRemoveIcon = () => { + setIconUrl(null); + onRemoveImage(entity.id); + }; - handleUploadPic(file) { - const formData = new FormData(); - formData.append("file", file); - this.props.onUploadImage(this.state.entity, formData); - } - - toggleSection(section, ev) { - const { showSection } = this.state; - const newShowSection = showSection === section ? "main" : section; - ev.preventDefault(); - - this.setState({ showSection: newShowSection }); - } - - handleEditSubCategory(id) { - const { history, currentSummit } = this.props; - history.push(`/app/summits/${currentSummit.id}/event-categories/${id}`); - } + const availableSubTracks = currentSummit.tracks.filter( + (t) => + !t.parent_id && + !entity.subtracks?.map((st) => st.id).includes(t.id) && + t.id !== entity.id + ); - handleLinkSubCategory() { - const { entity, subtrack } = this.state; - this.setState({ subtrack: null }); - this.props.onLinkSubCategory(entity.id, subtrack); - } - - handleUnlinkSubCategory(subtrackId) { - const { entity } = this.state; - this.props.onUnlinkSubCategory(entity.id, subtrackId); - } + const handleAddSubtrack = () => { + if (!subtrackToLink) return; + onLinkSubCategory(entity.id, subtrackToLink.id); + setSubtrackToLink(null); + }; - handleUpdateSubCategoryOrder(subtracks, subtrackId, newOrder) { - const { entity } = this.state; - this.props.onUpdateSubCategoryOrder(entity.id, subtrackId, newOrder); - } + const handleEditSubtrack = (subtrack) => + history.push( + `/app/summits/${currentSummit.id}/event-categories/${subtrack.id}` + ); - render() { - const { entity, errors, showSection } = this.state; - const { currentSummit } = this.props; + const handleUnlinkSubtrack = (subtrackId) => + onUnlinkSubCategory(entity.id, subtrackId); - const availableSubTracks = currentSummit.tracks.filter( - (t) => - !t.parent_id && - !entity.subtracks.map((t) => t.id).includes(t.id) && - t.id !== entity.id - ); + const handleReorderSubtracks = (_newOrder, subtrackId, newSubtrackOrder) => + onUpdateSubCategoryOrder(entity.id, subtrackId, newSubtrackOrder); - const table_options = { - actions: { - edit: { onClick: this.handleEditSubCategory }, - delete: { onClick: this.handleUnlinkSubCategory } - } - }; + return ( + + + + + + {T.translate("edit_event_category.name")} * + + + + + + {T.translate("edit_event_category.code")} * + + + + + + {T.translate("edit_event_category.color")} * + + + + + + {T.translate("edit_event_category.text_color")} * + + + + - const columns = [ - { columnKey: "id", value: T.translate("general.id") }, - { columnKey: "name", value: T.translate("event_category_list.name") }, - { columnKey: "code", value: T.translate("event_category_list.code") }, - { columnKey: "color", value: T.translate("event_category_list.color") } - ]; + + + + {T.translate("edit_event_category.description")} * + + + + - return ( -
- -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
- - -
-
-
-
- - + + + {T.translate("edit_event_category.number_sessions")} + + -
-
- - + -
-
- - + -
-
- - + -
-
-
-
-
- - -
-
-
-
- - -
-
-
+ + -
+ + + + + + + + -
-
- - + + + {T.translate("edit_event_category.tags")} + + + queryTags(currentSummit.id, input, callback) + } + formatOption={(tag) => ({ + value: tag.id.toString(), + label: tag.tag + })} + formatSelectedValue={(s) => ({ value: s.value, label: s.label })} /> -
-
-
-
-