diff --git a/src/actions/selection-plan-actions.js b/src/actions/selection-plan-actions.js index e0e48ae77..4ab993a52 100644 --- a/src/actions/selection-plan-actions.js +++ b/src/actions/selection-plan-actions.js @@ -12,7 +12,7 @@ * */ import T from "i18n-react/dist/i18n-react"; -import debounce from "lodash/debounce" +import debounce from "lodash/debounce"; import { getRequest, putRequest, @@ -35,7 +35,12 @@ import { fetchErrorHandler } from "../utils/methods"; import { saveMarketingSetting } from "./marketing-actions"; -import { DEBOUNCE_WAIT, DEFAULT_PER_PAGE } from "../utils/constants"; +import { + DEBOUNCE_WAIT, + DEFAULT_CURRENT_PAGE, + DEFAULT_ORDER_DIR, + DEFAULT_PER_PAGE +} from "../utils/constants"; URI.escapeQuerySpace = false; @@ -66,7 +71,13 @@ export const SELECTION_PLAN_PROGRESS_FLAG_ORDER_UPDATED = "SELECTION_PLAN_PROGRESS_FLAG_ORDER_UPDATED"; export const getSelectionPlans = - (term = "", page = 1, order = "id", orderDir = 1) => + ( + term = "", + page = DEFAULT_CURRENT_PAGE, + perPage = DEFAULT_PER_PAGE, + order = "id", + orderDir = DEFAULT_ORDER_DIR + ) => async (dispatch, getState) => { const { currentSummitState } = getState(); const accessToken = await getAccessTokenSafely(); @@ -84,7 +95,7 @@ export const getSelectionPlans = access_token: accessToken, relations: "none", page, - per_page: DEFAULT_PER_PAGE, + per_page: perPage, order: `${orderDir === 1 ? "" : "-"}${order}` }; @@ -93,10 +104,11 @@ export const getSelectionPlans = } return getRequest( - null, + createAction(REQUEST_SELECTION_PLANS), createAction(RECEIVE_SELECTION_PLANS), `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/selection-plans`, - authErrorHandler + authErrorHandler, + { order, orderDir, page, perPage, term } )(params)(dispatch).then(async () => { dispatch(stopLoading()); }); diff --git a/src/components/forms/selection-plan-form.js b/src/components/forms/selection-plan-form.js index eae94c098..b57e300ce 100644 --- a/src/components/forms/selection-plan-form.js +++ b/src/components/forms/selection-plan-form.js @@ -11,27 +11,28 @@ * limitations under the License. * */ -import React from "react"; +import React, { useState, useEffect, useRef } from "react"; import T from "i18n-react/dist/i18n-react"; import "awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css"; import { epochToMomentTimeZone } from "openstack-uicore-foundation/lib/utils/methods"; import { queryTrackGroups, - queryEventTypes, - queryMembers + queryEventTypes } from "openstack-uicore-foundation/lib/utils/query-actions"; -import { - Input, - DateTimePicker, - SimpleLinkList, - SortableTable, - Panel, - Table, - Dropdown -} from "openstack-uicore-foundation/lib/components"; +import Input from "openstack-uicore-foundation/lib/components/inputs/text-input"; +import DateTimePicker from "openstack-uicore-foundation/lib/components/inputs/datetimepicker"; +import SimpleLinkList from "openstack-uicore-foundation/lib/components/simple-link-list"; +import SortableTable from "openstack-uicore-foundation/lib/components/mui/sortable-table"; +import Panel from "openstack-uicore-foundation/lib/components/sections/panel"; +import Table from "openstack-uicore-foundation/lib/components/mui/table"; +import Dropdown from "openstack-uicore-foundation/lib/components/inputs/dropdown"; import TextEditorV3 from "openstack-uicore-foundation/lib/components/inputs/editor-input-v3"; import Switch from "react-switch"; import { Pagination } from "react-bootstrap"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import Checkbox from "@mui/material/Checkbox"; +import FormControlLabel from "@mui/material/FormControlLabel"; import { isEmpty, scrollToError, @@ -52,112 +53,83 @@ import { DEFAULT_ALLOWED_QUESTIONS, DEFAULT_CFP_PRESENTATION_EDITION_TABS } from "../../reducers/selection_plans/selection-plan-reducer"; -import history from "../../history"; - -class SelectionPlanForm extends React.Component { - constructor(props) { - super(props); - - this.state = { - entity: { ...props.entity }, - errors: props.errors, - showSection: "main", - newMemberEmail: "", - showImportModal: false, - importFile: null - }; - - this.handleTrackGroupLink = this.handleTrackGroupLink.bind(this); - this.handleTrackGroupUnLink = this.handleTrackGroupUnLink.bind(this); - this.handleChange = this.handleChange.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); - this.handleEditExtraQuestion = this.handleEditExtraQuestion.bind(this); - this.handleDeleteExtraQuestion = this.handleDeleteExtraQuestion.bind(this); - this.handleNewExtraQuestion = this.handleNewExtraQuestion.bind(this); - this.handleDeleteEventType = this.handleDeleteEventType.bind(this); - this.handleAddEventType = this.handleAddEventType.bind(this); - this.handleAddRatingType = this.handleAddRatingType.bind(this); - this.handleDeleteRatingType = this.handleDeleteRatingType.bind(this); - this.handleEditRatingType = this.handleEditRatingType.bind(this); - this.handleRemoveProgressFlag = this.handleRemoveProgressFlag.bind(this); - this.toggleSection = this.toggleSection.bind(this); - this.handleNotificationEmailTemplateChange = - this.handleNotificationEmailTemplateChange.bind(this); - this.fetchSummitSelectionPlanExtraQuestions = - this.fetchSummitSelectionPlanExtraQuestions.bind(this); - this.fetchMembers = this.fetchMembers.bind(this); - this.linkSummitSelectionPlanExtraQuestion = - this.linkSummitSelectionPlanExtraQuestion.bind(this); - this.fetchSummitPresentationActionTypes = - this.fetchSummitPresentationActionTypes.bind(this); - this.linkSummitProgressFlag = this.linkSummitProgressFlag.bind(this); - this.handleAddAllowedMember = this.handleAddAllowedMember.bind(this); - this.handleImportAllowedMembers = - this.handleImportAllowedMembers.bind(this); - this.handleDeleteAllowedMember = this.handleDeleteAllowedMember.bind(this); - this.handleAllowedMembersPageChange = - this.handleAllowedMembersPageChange.bind(this); - this.handleOnSwitchChange = this.handleOnSwitchChange.bind(this); - } - fetchSummitSelectionPlanExtraQuestions(input, callback) { - const { currentSummit } = this.props; - - if (!input) { - return Promise.resolve({ options: [] }); +const SelectionPlanForm = (props) => { + const { + entity: propsEntity, + errors: propsErrors, + currentSummit, + extraQuestionsOrderDir, + extraQuestionsOrder, + actionTypesOrderDir, + actionTypesOrder, + allowedMembers, + onSaved, + onSubmit, + saveSelectionPlanSettings, + onTrackGroupLink, + onTrackGroupUnLink, + onAddEventType, + onDeleteEventType, + onAddRatingType, + onEditRatingType, + onDeleteRatingType, + onEditExtraQuestion, + onDeleteExtraQuestion, + onAddNewExtraQuestion, + onAssignExtraQuestion2SelectionPlan, + onAssignProgressFlag2SelectionPlan, + onUnassignProgressFlag, + onUpdateProgressFlagOrder, + onUpdateRatingTypeOrder, + updateExtraQuestionOrder, + onImportAllowedMembers, + onAllowedMemberAdd, + onAllowedMemberDelete, + onAllowedMembersPageChange + } = props; + + const [entity, setEntity] = useState({ ...propsEntity }); + const [errors, setErrors] = useState(propsErrors); + const [showSection, setShowSection] = useState("main"); + const [newMemberEmail, setNewMemberEmail] = useState(""); + const [showImportModal, setShowImportModal] = useState(false); + + const prevPropsRef = useRef({ entity: propsEntity, errors: propsErrors }); + + useEffect(() => { + const prevEntity = prevPropsRef.current.entity; + const prevErrors = prevPropsRef.current.errors; + + scrollToError(propsErrors); + + const updates = {}; + + if (!shallowEqual(prevEntity, propsEntity)) { + updates.entity = { ...propsEntity }; + updates.errors = {}; } - querySelectionPlanExtraQuestions(currentSummit.id, input, callback); - } - fetchMembers(input, callback) { - if (!input) { - return Promise.resolve({ options: [] }); + if (!shallowEqual(prevErrors, propsErrors)) { + updates.errors = { ...propsErrors }; } - queryMembers(input, callback); - } - - linkSummitSelectionPlanExtraQuestion(question) { - const { currentSummit } = this.props; - this.props.onAssignExtraQuestion2SelectionPlan( - currentSummit.id, - this.state.entity.id, - question.id - ); - } - - handleEditExtraQuestion(questionId) { - this.props.onEditExtraQuestion(questionId); - } - - handleDeleteExtraQuestion(questionId) { - this.props.onDeleteExtraQuestion(questionId); - } - - handleNewExtraQuestion() { - this.props.onAddNewExtraQuestion(); - } - componentDidUpdate(prevProps) { - const state = {}; - scrollToError(this.props.errors); - - if (!shallowEqual(prevProps.entity, this.props.entity)) { - state.entity = { ...this.props.entity }; - state.errors = {}; + if (!isEmpty(updates)) { + if (updates.entity) setEntity(updates.entity); + if (updates.errors !== undefined) setErrors(updates.errors); } - if (!shallowEqual(prevProps.errors, this.props.errors)) { - state.errors = { ...this.props.errors }; - } + prevPropsRef.current = { entity: propsEntity, errors: propsErrors }; + }, [propsEntity, propsErrors]); - if (!isEmpty(state)) { - this.setState({ ...this.state, ...state }); - } - } + const hasErrors = (field) => { + if (field in errors) return errors[field]; + return ""; + }; - handleChange(ev) { - const newEntity = { ...this.state.entity }; - const newErrors = { ...this.state.errors }; + const handleChange = (ev) => { + const newEntity = { ...entity }; + const newErrors = { ...errors }; let { value, id } = ev.target; if (ev.target.type === "checkbox") { @@ -169,7 +141,9 @@ class SelectionPlanForm extends React.Component { } if (id.startsWith("cfp_")) { - if (!newEntity.marketing_settings.hasOwnProperty(id)) { + if ( + !Object.prototype.hasOwnProperty.call(newEntity.marketing_settings, id) + ) { newEntity.marketing_settings[id] = { value: "" }; } newEntity.marketing_settings[id].value = value; @@ -178,1299 +152,1197 @@ class SelectionPlanForm extends React.Component { newEntity[id] = value; } - this.setState({ entity: newEntity, errors: newErrors }); - } + setEntity(newEntity); + setErrors(newErrors); + }; - handleNotificationEmailTemplateChange(ev) { - const newEntity = { ...this.state.entity }; - const newErrors = { ...this.state.errors }; + const handleNotificationEmailTemplateChange = (ev) => { const { value, id } = ev.target; + setEntity((prev) => ({ ...prev, [id]: value })); + setErrors((prev) => ({ ...prev, [id]: "" })); + }; - newErrors[id] = ""; - newEntity[id] = value; - this.setState({ ...this.state, entity: newEntity, errors: newErrors }); - } - - handleSubmit(ev) { + const handleSubmit = (ev) => { ev.preventDefault(); + return onSubmit(entity) + .then((e) => { + if (!e?.id) return null; + return saveSelectionPlanSettings(entity.marketing_settings, e.id).then( + () => { + if (onSaved) onSaved(e); + } + ); + }) + .catch(() => { + // errors are surfaced via error handler + }); + }; + + const handleTrackGroupLink = (value) => onTrackGroupLink(entity.id, value); + const handleTrackGroupUnLink = (valueId) => + onTrackGroupUnLink(entity.id, valueId); + const handleAddEventType = (value) => onAddEventType(entity.id, value); + const handleDeleteEventType = (valueId) => + onDeleteEventType(entity.id, valueId); + const handleAddRatingType = () => onAddRatingType(); + const handleEditRatingType = (ratingTypeId) => onEditRatingType(ratingTypeId); + const handleDeleteRatingType = (ratingTypeId) => + onDeleteRatingType(ratingTypeId); + const handleEditExtraQuestion = (questionId) => + onEditExtraQuestion(questionId); + const handleDeleteExtraQuestion = (questionId) => + onDeleteExtraQuestion(questionId); + const handleNewExtraQuestion = () => onAddNewExtraQuestion(); + const handleRemoveProgressFlag = (progressFlagId) => + onUnassignProgressFlag(progressFlagId); + const handleDeleteAllowedMember = (valueId) => + onAllowedMemberDelete(entity.id, valueId); + const handleAllowedMembersPageChange = (page) => + onAllowedMembersPageChange(entity.id, page); + + const handleAddAllowedMember = () => + onAllowedMemberAdd(entity.id, newMemberEmail); + + const handleImportAllowedMembers = (importFile) => { + if (importFile) onImportAllowedMembers(entity.id, importFile); + setShowImportModal(false); + }; + + const fetchSummitSelectionPlanExtraQuestions = (input, callback) => { + if (!input) return Promise.resolve({ options: [] }); + querySelectionPlanExtraQuestions(currentSummit.id, input, callback); + }; - const entity = { ...this.state.entity }; - const { currentSummit } = this.props; - - this.props.onSubmit(this.state.entity).then((e) => { - this.props - .saveSelectionPlanSettings(entity.marketing_settings, e.id) - .then(() => { - if (!entity.id) - history.push( - `/app/summits/${currentSummit.id}/selection-plans/${e.id}` - ); - }); - }); - } - - hasErrors(field) { - const { errors } = this.state; - if (field in errors) { - return errors[field]; - } - - return ""; - } - - handleTrackGroupLink(value) { - const { entity } = this.state; - this.props.onTrackGroupLink(entity.id, value); - } - - handleTrackGroupUnLink(valueId) { - const { entity } = this.state; - this.props.onTrackGroupUnLink(entity.id, valueId); - } - - handleAddEventType(value) { - const { entity } = this.state; - this.props.onAddEventType(entity.id, value); - } - - handleDeleteEventType(valueId) { - const { entity } = this.state; - this.props.onDeleteEventType(entity.id, valueId); - } - - handleAddRatingType() { - this.props.onAddRatingType(); - } - - handleEditRatingType(ratingTypeId) { - this.props.onEditRatingType(ratingTypeId); - } - - handleDeleteRatingType(ratingTypeId) { - this.props.onDeleteRatingType(ratingTypeId); - } - - fetchSummitPresentationActionTypes(input, callback) { - const { currentSummit } = this.props; + const linkSummitSelectionPlanExtraQuestion = (question) => { + onAssignExtraQuestion2SelectionPlan( + currentSummit.id, + entity.id, + question.id + ); + }; - if (!input) { - return Promise.resolve({ options: [] }); - } + const fetchSummitPresentationActionTypes = (input, callback) => { + if (!input) return Promise.resolve({ options: [] }); querySummitProgressFlags(currentSummit.id, input, callback); - } + }; - linkSummitProgressFlag(progressFlag) { - const { currentSummit } = this.props; - this.props.onAssignProgressFlag2SelectionPlan( + const linkSummitProgressFlag = (progressFlag) => { + onAssignProgressFlag2SelectionPlan( currentSummit.id, - this.state.entity.id, + entity.id, progressFlag.id ); - } - - handleRemoveProgressFlag(progressFlagId) { - this.props.onUnassignProgressFlag(progressFlagId); - } - - handleImportAllowedMembers(importFile) { - if (importFile) { - this.props.onImportAllowedMembers(this.state.entity.id, importFile); - } - this.setState({ ...this.state, showImportModal: false }); - } - - handleAddAllowedMember() { - const { entity, newMemberEmail } = this.state; - this.props.onAllowedMemberAdd(entity.id, newMemberEmail); - } - - handleDeleteAllowedMember(valueId) { - const { entity } = this.state; - this.props.onAllowedMemberDelete(entity.id, valueId); - } - - handleAllowedMembersPageChange(page) { - const { entity } = this.state; - this.props.onAllowedMembersPageChange(entity.id, page); - } - - toggleSection(section) { - const { showSection } = this.state; - const newShowSection = showSection === section ? "main" : section; - this.setState({ showSection: newShowSection }); - } - - handleOnSwitchChange(setting, value) { - const newEntity = { ...this.state.entity }; - const newErrors = { ...this.state.errors }; - - if (!newEntity.marketing_settings.hasOwnProperty(setting)) { + }; + + const handleOnSwitchChange = (setting, value) => { + const newEntity = { ...entity }; + if ( + !Object.prototype.hasOwnProperty.call( + newEntity.marketing_settings, + setting + ) + ) { newEntity.marketing_settings[setting] = { value: "" }; } - newEntity.marketing_settings[setting].value = value; - - this.setState({ entity: newEntity, errors: newErrors }); - } - - render() { - const { entity, showSection, newMemberEmail, showImportModal } = this.state; - const { - currentSummit, - extraQuestionsOrderDir, - extraQuestionsOrder, - actionTypesOrderDir, - actionTypesOrder, - allowedMembers - } = this.props; - - const trackGroupsColumns = [ - { columnKey: "name", value: T.translate("edit_selection_plan.name") }, - { - columnKey: "description", - value: T.translate("edit_selection_plan.description") - } - ]; - - const trackGroupsOptions = { - valueKey: "name", - labelKey: "name", - defaultOptions: true, - actions: { - search: (input, callback) => { - queryTrackGroups(currentSummit.id, input, callback); - }, - delete: { onClick: this.handleTrackGroupUnLink }, - add: { onClick: this.handleTrackGroupLink } - } - }; - - const eventTypesColumns = [ - { columnKey: "name", value: T.translate("edit_selection_plan.name") } - ]; - - const eventTypesOptions = { - valueKey: "name", - labelKey: "name", - defaultOptions: true, - actions: { - search: (input, callback) => { - queryEventTypes( - currentSummit.id, - input, - callback, - PresentationTypeClassName - ); - }, - delete: { onClick: this.handleDeleteEventType }, - add: { onClick: this.handleAddEventType } - } - }; - - const extraQuestionColumns = [ - { - columnKey: "type", - value: T.translate("order_extra_question_list.question_type") + setEntity(newEntity); + }; + + const toggleSection = (section) => { + setShowSection((prev) => (prev === section ? "main" : section)); + }; + + const trackGroupsColumns = [ + { columnKey: "name", value: T.translate("edit_selection_plan.name") }, + { + columnKey: "description", + value: T.translate("edit_selection_plan.description") + } + ]; + + const trackGroupsOptions = { + valueKey: "name", + labelKey: "name", + defaultOptions: true, + actions: { + search: (input, callback) => { + queryTrackGroups(currentSummit.id, input, callback); }, - { - columnKey: "label", - value: T.translate("order_extra_question_list.visible_question") + delete: { onClick: handleTrackGroupUnLink }, + add: { onClick: handleTrackGroupLink } + } + }; + + const eventTypesColumns = [ + { columnKey: "name", value: T.translate("edit_selection_plan.name") } + ]; + + const eventTypesOptions = { + valueKey: "name", + labelKey: "name", + defaultOptions: true, + actions: { + search: (input, callback) => { + queryEventTypes( + currentSummit.id, + input, + callback, + PresentationTypeClassName + ); }, - { - columnKey: "name", - value: T.translate("order_extra_question_list.question_id") - } - ]; - - const extraQuestionsOptions = { - sortCol: extraQuestionsOrder, - sortDir: extraQuestionsOrderDir, - actions: { - edit: { onClick: this.handleEditExtraQuestion }, - delete: { onClick: this.handleDeleteExtraQuestion } - } - }; - - const ratingTypesColumns = [ - { columnKey: "name", value: T.translate("rating_type_list.name") }, - { columnKey: "weight", value: T.translate("rating_type_list.weight") } - ]; - - const ratingTypesOptions = { - actions: { - edit: { onClick: this.handleEditRatingType }, - delete: { onClick: this.handleDeleteRatingType } - } - }; - - const actionTypesColumns = [ - { columnKey: "label", value: T.translate("progress_flags.label") } - ]; - - const actionTypesOptions = { - sortCol: actionTypesOrder, - sortDir: actionTypesOrderDir, - actions: { - delete: { onClick: this.handleRemoveProgressFlag } - } - }; + delete: { onClick: handleDeleteEventType }, + add: { onClick: handleAddEventType } + } + }; + + const extraQuestionColumns = [ + { + columnKey: "type", + header: T.translate("order_extra_question_list.question_type") + }, + { + columnKey: "label", + header: T.translate("order_extra_question_list.visible_question") + }, + { + columnKey: "name", + header: T.translate("order_extra_question_list.question_id") + } + ]; + + const extraQuestionsOptions = { + sortCol: extraQuestionsOrder, + sortDir: extraQuestionsOrderDir, + actions: { + edit: { onClick: handleEditExtraQuestion }, + delete: { onClick: handleDeleteExtraQuestion } + } + }; - const allowedMembersColumns = [ - { columnKey: "id", value: T.translate("edit_selection_plan.id") }, - { columnKey: "email", value: T.translate("edit_selection_plan.email") } - ]; + const ratingTypesColumns = [ + { columnKey: "name", header: T.translate("rating_type_list.name") }, + { columnKey: "weight", header: T.translate("rating_type_list.weight") } + ]; - const allowedMembersOptions = { - sortCol: "email", - sortDir: 1, - actions: { - delete: { onClick: this.handleDeleteAllowedMember } - } - }; + const ratingTypesOptions = { + actions: { + edit: { onClick: handleEditRatingType }, + delete: { onClick: handleDeleteRatingType } + } + }; - console.log("CHECK...", entity, currentSummit); + const actionTypesColumns = [ + { columnKey: "label", header: T.translate("progress_flags.label") } + ]; - return ( -