diff --git a/src/actions/sponsor-users-actions.js b/src/actions/sponsor-users-actions.js index 84db7ab48..6360ebca1 100644 --- a/src/actions/sponsor-users-actions.js +++ b/src/actions/sponsor-users-actions.js @@ -29,7 +29,8 @@ import { DEFAULT_CURRENT_PAGE, DEFAULT_ORDER_DIR, DEFAULT_PER_PAGE, - DUMMY_ACTION + DUMMY_ACTION, + SPONSOR_USER_ASSIGNMENT_TYPE } from "../utils/constants"; import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions"; @@ -328,9 +329,11 @@ export const processSponsorUserRequest = (request) => async (dispatch) => { send_activation_email: request.send_email }; - if (request.sponsor?.id) payload.sponsor_id = request.sponsor.id; - else { - if (request.company?.id) payload.company_id = request.company.id; + if (request.sponsor_type === SPONSOR_USER_ASSIGNMENT_TYPE.EXISTING) + payload.sponsor_id = request.sponsor.id; + + if (request.sponsor_type === SPONSOR_USER_ASSIGNMENT_TYPE.NEW) { + if (request.company?.id > 0) payload.company_id = request.company.id; else payload.company_name = request.company.name; payload.sponsorship_types = request.tiers.map((st) => st.id); } diff --git a/src/components/mui/formik-inputs/company-input-mui.js b/src/components/mui/formik-inputs/company-input-mui.js index 8bf851beb..301a451a0 100644 --- a/src/components/mui/formik-inputs/company-input-mui.js +++ b/src/components/mui/formik-inputs/company-input-mui.js @@ -14,19 +14,22 @@ import React, { useState, useEffect, useMemo } from "react"; import PropTypes from "prop-types"; import TextField from "@mui/material/TextField"; -import Autocomplete from "@mui/material/Autocomplete"; +import Autocomplete, { createFilterOptions } from "@mui/material/Autocomplete"; import CircularProgress from "@mui/material/CircularProgress"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import { useField } from "formik"; import { queryCompanies } from "../../../actions/company-actions"; import { DEBOUNCE_WAIT_250 } from "../../../utils/constants"; +const filter = createFilterOptions(); + const CompanyInputMUI = ({ id, name, placeholder, plainValue, isMulti = false, + allowCreate = false, ...rest }) => { const [field, meta, helpers] = useField(name); @@ -34,6 +37,7 @@ const CompanyInputMUI = ({ const [open, setOpen] = useState(false); const [inputValue, setInputValue] = useState(""); const [loading, setLoading] = useState(false); + const [isDebouncing, setIsDebouncing] = useState(false); const { value } = field; const error = meta.touched && meta.error; @@ -44,6 +48,7 @@ const CompanyInputMUI = ({ return; } + setIsDebouncing(false); setLoading(true); const normalize = (results) => @@ -60,11 +65,13 @@ const CompanyInputMUI = ({ useEffect(() => { if (inputValue) { + setIsDebouncing(true); const delayDebounce = setTimeout(() => { fetchOptions(inputValue); }, DEBOUNCE_WAIT_250); return () => clearTimeout(delayDebounce); } + setIsDebouncing(false); }, [inputValue]); const selectedValue = useMemo(() => { @@ -96,13 +103,43 @@ const CompanyInputMUI = ({ })); } else { theValue = plainValue - ? newValue.label - : { id: parseInt(newValue.value), name: newValue.label }; + ? newValue.inputValue || newValue.label + : { + id: newValue.inputValue ? 0 : parseInt(newValue.value), + name: newValue.inputValue || newValue.label + }; } helpers.setValue(theValue); }; + const handleFilterOptions = (options, params) => { + const filtered = filter(options, params); + + if (!allowCreate || loading || isDebouncing) return filtered; + + const { inputValue } = params; + const isExisting = options.some( + (option) => inputValue.toLowerCase() === option.label.toLowerCase() + ); + + if (inputValue !== "" && !isExisting) { + filtered.push({ + inputValue, + value: null, + label: `Create "${inputValue}"` + }); + } + return filtered; + }; + + const getOptionLabel = (option) => { + if (option.inputValue) { + return option.inputValue; + } + return option.label || ""; + }; + return ( setOpen(false)} options={options} value={selectedValue} - getOptionLabel={(option) => option.label} + getOptionLabel={getOptionLabel} isOptionEqualToValue={(option, value) => option.value === value.value} onInputChange={(_, newInputValue) => { setInputValue(newInputValue); }} + filterOptions={handleFilterOptions} multiple={isMulti} onChange={handleChange} loading={loading} fullWidth popupIcon={} + renderOption={(props, option) => ( +
  • + {option.label} +
  • + )} renderInput={(params) => ( { const normalized = { ...data }; @@ -32,9 +33,11 @@ const buildInitialValues = (data) => { normalized.access_rights = []; normalized.send_email = true; + normalized.sponsor_type = SPONSOR_USER_ASSIGNMENT_TYPE.NEW; if (data.company_id !== 0 && data.sponsor) - normalized.sponsor_type = "existing"; - else if (data.company_name) normalized.sponsor_type = "new"; + normalized.sponsor_type = SPONSOR_USER_ASSIGNMENT_TYPE.EXISTING; + else if (data.company_name) + normalized.sponsor_type = SPONSOR_USER_ASSIGNMENT_TYPE.NEW; return normalized; }; @@ -50,7 +53,7 @@ const ProcessRequestForm = ({ request, userGroups, summit, onSubmit }) => { .object() .nullable() .when("sponsor_type", { - is: "existing", + is: SPONSOR_USER_ASSIGNMENT_TYPE.EXISTING, then: (schema) => schema.required(T.translate("validation.required")).shape({ id: yup.number().required(), @@ -62,10 +65,13 @@ const ProcessRequestForm = ({ request, userGroups, summit, onSubmit }) => { .object() .nullable() .when("sponsor_type", { - is: "new", + is: SPONSOR_USER_ASSIGNMENT_TYPE.NEW, then: (schema) => schema.required(T.translate("validation.required")).shape({ - id: yup.number().required(), + id: yup + .number() + .min(0, T.translate("validation.required")) + .required(), name: yup.string().required() }), otherwise: (schema) => schema.notRequired() @@ -79,7 +85,7 @@ const ProcessRequestForm = ({ request, userGroups, summit, onSubmit }) => { }) ) .when("sponsor_type", { - is: "new", + is: SPONSOR_USER_ASSIGNMENT_TYPE.NEW, then: (schema) => schema .min(1, T.translate("validation.required", { count: 1 })) @@ -177,13 +183,13 @@ const ProcessRequestForm = ({ request, userGroups, summit, onSubmit }) => { }} options={[ { - value: "existing", + value: SPONSOR_USER_ASSIGNMENT_TYPE.EXISTING, label: T.translate( "sponsor_users.process_request.assign_to_existing" ) }, { - value: "new", + value: SPONSOR_USER_ASSIGNMENT_TYPE.NEW, label: T.translate( "sponsor_users.process_request.assign_to_new" ) @@ -194,7 +200,10 @@ const ProcessRequestForm = ({ request, userGroups, summit, onSubmit }) => { { - {formik.values.sponsor_type === "new" && ( + {formik.values.sponsor_type === SPONSOR_USER_ASSIGNMENT_TYPE.NEW && ( <> {T.translate("sponsor_users.process_request.show_details")} diff --git a/src/utils/constants.js b/src/utils/constants.js index 93939c429..738fefa11 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -256,4 +256,9 @@ export const PURCHASE_STATUS = { PENDING: "Pending", PAID: "Paid", CANCELLED: "Cancelled" +} + +export const SPONSOR_USER_ASSIGNMENT_TYPE = { + EXISTING: "existing", + NEW: "new" };