diff --git a/src/actions/event-category-actions.js b/src/actions/event-category-actions.js index 95bfcaf46..41db09473 100644 --- a/src/actions/event-category-actions.js +++ b/src/actions/event-category-actions.js @@ -489,7 +489,7 @@ export const saveEventCategoryGroup = const params = { access_token: accessToken }; if (entity.id) { - putRequest( + return putRequest( createAction(UPDATE_EVENT_CATEGORY_GROUP), createAction(EVENT_CATEGORY_GROUP_UPDATED), `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/track-groups/${entity.id}`, @@ -503,30 +503,22 @@ export const saveEventCategoryGroup = ) ); }); - } else { - const successMessage = { - title: T.translate("general.done"), - html: T.translate("edit_event_category_group.group_created"), - type: "success" - }; - - postRequest( + } + return postRequest( createAction(UPDATE_EVENT_CATEGORY_GROUP), createAction(EVENT_CATEGORY_GROUP_ADDED), `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/track-groups`, normalizedEntity, authErrorHandler, entity - )(params)(dispatch).then((payload) => { + )(params)(dispatch).then(() => { dispatch( - showMessage(successMessage, () => { - history.replace( - `/app/summits/${currentSummit.id}/event-category-groups/${payload.response.id}` - ); - }) + showSuccessMessage( + T.translate("edit_event_category_group.group_created") + ) ); }); - } + }; export const deleteEventCategoryGroup = diff --git a/src/components/forms/event-category-group-form.js b/src/components/forms/event-category-group-form.js index 3c0f9abfd..01b369666 100644 --- a/src/components/forms/event-category-group-form.js +++ b/src/components/forms/event-category-group-form.js @@ -1,4 +1,4 @@ -/* * +/** * Copyright 2017 OpenStack Foundation * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,360 +11,520 @@ * limitations under the License. * */ -import React from "react"; +import React, { useState } from "react"; import T from "i18n-react/dist/i18n-react"; -import "awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css"; +import PropTypes from "prop-types"; +import { FormikProvider, useFormik } from "formik"; +import * as yup from "yup"; +import Button from "@mui/material/Button"; +import Box from "@mui/material/Box"; +import Divider from "@mui/material/Divider"; +import Grid2 from "@mui/material/Grid2"; +import Typography from "@mui/material/Typography"; +import { DateTimePicker } from "@mui/x-date-pickers/DateTimePicker"; +import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; +import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment"; import { epochToMomentTimeZone } from "openstack-uicore-foundation/lib/utils/methods"; import { queryTracks, queryGroups } from "openstack-uicore-foundation/lib/utils/query-actions"; -import { - Input, - SimpleLinkList, - Dropdown, - DateTimePicker -} from "openstack-uicore-foundation/lib/components"; +import MuiTable from "openstack-uicore-foundation/lib/components/mui/table"; +import MuiFormikTextField from "openstack-uicore-foundation/lib/components/mui/formik-inputs/textfield"; +import MuiFormikSelectV2 from "openstack-uicore-foundation/lib/components/mui/formik-inputs/select-v2"; import TextEditorV3 from "openstack-uicore-foundation/lib/components/inputs/editor-input-v3"; -import Swal from "sweetalert2"; -import { - isEmpty, - scrollToError, - shallowEqual, - hasErrors -} from "../../utils/methods"; - -class EventCategoryGroupForm extends React.Component { - constructor(props) { - super(props); +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 } from "../../utils/yup"; - this.state = { - entity: { ...props.entity }, - errors: props.errors - }; +const DEFAULT_PER_PAGE = 10; - this.handleChange = this.handleChange.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); - this.handleTrackLink = this.handleTrackLink.bind(this); - this.handleTrackUnLink = this.handleTrackUnLink.bind(this); - this.handleAllowedGroupLink = this.handleAllowedGroupLink.bind(this); - this.handleAllowedGroupUnLink = this.handleAllowedGroupUnLink.bind(this); - } +const EventCategoryGroupForm = ({ + entity: initialEntity, + allClasses, + currentSummit, + onSubmit, + onTrackLink, + onTrackUnLink, + onAllowedGroupLink, + onAllowedGroupUnLink +}) => { + const [isSaving, setIsSaving] = useState(false); + const [trackState, setTrackState] = useState({ + page: 1, + perPage: DEFAULT_PER_PAGE + }); + const [groupState, setGroupState] = useState({ + page: 1, + perPage: DEFAULT_PER_PAGE + }); - componentDidUpdate(prevProps) { - const state = {}; - scrollToError(this.props.errors); + const trackSearchFormik = useFormik({ + initialValues: { track: null }, + onSubmit: () => {} + }); + const groupSearchFormik = useFormik({ + initialValues: { group: null }, + onSubmit: () => {} + }); - if (!shallowEqual(prevProps.entity, this.props.entity)) { - state.entity = { ...this.props.entity }; - state.errors = {}; - } + const isNew = !initialEntity?.id; - if (!shallowEqual(prevProps.errors, this.props.errors)) { - state.errors = { ...this.props.errors }; - } + const excludedTrackIds = initialEntity?.tracks?.map((t) => t.id) ?? []; + const queryTrackOptions = (input, callback) => + queryTracks(currentSummit.id, input, callback, excludedTrackIds); - if (!isEmpty(state)) { - this.setState({ ...this.state, ...state }); - } - } + const toEpoch = (momentValue) => { + if (!momentValue || !momentValue.isValid()) return 0; + return momentValue.unix(); + }; - handleChange(ev) { - const entity = { ...this.state.entity }; - const errors = { ...this.state.errors }; - let { value, id } = ev.target; + const fromEpoch = (epoch) => { + if (!epoch) return null; + return epochToMomentTimeZone(epoch, currentSummit.time_zone_id); + }; - if (ev.target.type === "datetime") { - value = value.unix(); + const formik = useFormik({ + initialValues: { + id: initialEntity?.id ?? 0, + class_name: initialEntity?.class_name ?? null, + name: initialEntity?.name ?? "", + color: initialEntity?.color ?? "", + begin_attendee_voting_period_date: + initialEntity?.begin_attendee_voting_period_date ?? 0, + end_attendee_voting_period_date: + initialEntity?.end_attendee_voting_period_date ?? 0, + max_attendee_votes: initialEntity?.max_attendee_votes ?? 0, + submission_begin_date: initialEntity?.submission_begin_date ?? 0, + submission_end_date: initialEntity?.submission_end_date ?? 0, + max_submission_allowed_per_user: + initialEntity?.max_submission_allowed_per_user ?? 0, + description: initialEntity?.description ?? "" + }, + enableReinitialize: true, + validationSchema: yup.object().shape({ + name: requiredStringValidation(), + class_name: yup + .string() + .nullable() + .required(T.translate("validation.required")) + }), + onSubmit: (values) => { + if (isSaving) return; + setIsSaving(true); + Promise.resolve(onSubmit(values)).finally(() => { + setIsSaving(false); + }); } + }); - errors[id] = ""; - entity[id] = value; - this.setState({ entity, errors }); - } + useScrollToError(formik, true); - handleSubmit(ev) { - ev.preventDefault(); - this.props.onSubmit(this.state.entity); - } + const selectedClass = allClasses.find( + (c) => c.class_name === formik.values.class_name + ); + const showSubmissionFields = selectedClass?.submission_begin_date ?? false; + const showAllowedGroups = selectedClass?.allowed_groups ?? false; - handleTrackLink(value) { - const { entity } = this.state; - this.props.onTrackLink(entity.id, value); - } + const classNameDdl = allClasses.map((c) => ({ + label: c.class_name, + value: c.class_name + })); - handleTrackUnLink(valueId) { - const { entity } = this.state; - const { onTrackUnLink } = this.props; + const tracksColumns = [ + { columnKey: "name", header: T.translate("edit_event_category.name") }, + { columnKey: "code", header: T.translate("edit_event_category.code") } + ]; - Swal.fire({ - title: T.translate("general.are_you_sure"), - text: T.translate("edit_event_category_group.unlink_track_warning"), - type: "warning", - showCancelButton: true, - confirmButtonText: T.translate("general.yes") - }).then((result) => { - if (result.value) { - onTrackUnLink(entity.id, valueId); - } - }); - } - - handleAllowedGroupLink(value) { - const { entity } = this.state; - this.props.onAllowedGroupLink(entity.id, value); - } - - handleAllowedGroupUnLink(valueId) { - const { entity } = this.state; - this.props.onAllowedGroupUnLink(entity.id, valueId); - } - - shouldShowField(flag) { - const { entity } = this.state; - if (!entity.class_name) return false; - const class_name = this.props.allClasses.find( - (c) => c.class_name === entity.class_name - ); - - return class_name[flag]; - } + const allowedGroupsColumns = [ + { columnKey: "title", header: T.translate("edit_event_category.name") }, + { + columnKey: "description", + header: T.translate("edit_event_category.description") + } + ]; - render() { - const { entity, errors } = this.state; - const { currentSummit, allClasses } = this.props; - const selectedTrackIds = entity?.tracks?.map((t) => t.id) || []; + const handleAddTrack = () => { + const track = trackSearchFormik.values.track?.raw; + if (!track) return; + onTrackLink(initialEntity.id, track); + trackSearchFormik.setFieldValue("track", null); + }; - const tracksColumns = [ - { columnKey: "name", value: T.translate("edit_event_category.name") }, - { columnKey: "code", value: T.translate("edit_event_category.code") } - ]; + const handleTrackDelete = (trackId) => { + onTrackUnLink(initialEntity.id, trackId); + }; - const tracksOptions = { - title: T.translate("edit_event_category_group.tracks"), - valueKey: "name", - labelKey: "name", - actions: { - search: (input, callback) => { - queryTracks(currentSummit.id, input, callback, selectedTrackIds); - }, - delete: { onClick: this.handleTrackUnLink }, - add: { onClick: this.handleTrackLink } - } - }; + const handleAddGroup = () => { + const group = groupSearchFormik.values.group?.raw; + if (!group) return; + onAllowedGroupLink(initialEntity.id, group); + groupSearchFormik.setFieldValue("group", null); + }; - const allowedGroupsColumns = [ - { columnKey: "title", value: T.translate("edit_event_category.name") }, - { - columnKey: "description", - value: T.translate("edit_event_category.description") - } - ]; + const handleAllowedGroupDelete = (groupId) => { + onAllowedGroupUnLink(initialEntity.id, groupId); + }; - const allowedGroupsOptions = { - title: T.translate("edit_event_category_group.allowed_groups"), - valueKey: "id", - labelKey: "title", - actions: { - search: queryGroups, - delete: { onClick: this.handleAllowedGroupUnLink }, - add: { onClick: this.handleAllowedGroupLink } - } - }; + const tracks = initialEntity?.tracks ?? []; + const paginatedTracks = tracks.slice( + (trackState.page - 1) * trackState.perPage, + trackState.page * trackState.perPage + ); - const class_name_ddl = allClasses.map((i) => ({ - label: i.class_name, - value: i.class_name - })); + const allowedGroups = initialEntity?.allowed_groups ?? []; + const paginatedGroups = allowedGroups.slice( + (groupState.page - 1) * groupState.perPage, + groupState.page * groupState.perPage + ); - return ( -
- ); - } -} +EventCategoryGroupForm.defaultProps = { + entity: null, + onTrackLink: () => {}, + onTrackUnLink: () => {}, + onAllowedGroupLink: () => {}, + onAllowedGroupUnLink: () => {} +}; export default EventCategoryGroupForm; diff --git a/src/i18n/en.json b/src/i18n/en.json index 0f46a5816..66acc075f 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -1410,7 +1410,9 @@ "group_created": "Activity Category Group created successfully.", "unlink_track_warning": "if you remove a category from the category group, any activities linked to that category will automatically be removed from the selection plan. Do you want to continue?", "placeholders": { - "select_class": "Select Class" + "select_class": "Select Class", + "search_categories": "Search and add categories", + "search_groups": "Search and add allowed groups" } }, "location_list": { diff --git a/src/layouts/event-category-group-layout.js b/src/layouts/event-category-group-layout.js index 496c0262b..8aa12a94a 100644 --- a/src/layouts/event-category-group-layout.js +++ b/src/layouts/event-category-group-layout.js @@ -9,7 +9,7 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - **/ + * */ import React from "react"; import { Switch, Route, withRouter } from "react-router-dom"; @@ -21,44 +21,36 @@ import EditEventCategoryGroupPage from "../pages/events/edit-event-category-grou import EventCategoryGroupListPage from "../pages/events/event-category-group-list-page"; import NoMatchPage from "../pages/no-match-page"; -class EventCategoryGroupLayout extends React.Component { - render() { - const { match } = this.props; - return ( -