mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-16 19:15:05 -05:00
WIP: adds survey refine for advanced logic
This commit is contained in:
@@ -15,7 +15,6 @@ import {
|
||||
TActionTextVariableCalculateOperator,
|
||||
TActionVariableValueType,
|
||||
TSurveyAdvancedLogic,
|
||||
ZAction,
|
||||
} from "@formbricks/types/surveys/logic";
|
||||
import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys/types";
|
||||
import {
|
||||
@@ -70,17 +69,19 @@ export function AdvancedLogicEditorActions({
|
||||
});
|
||||
};
|
||||
|
||||
function updateAction(actionIdx: number, updateActionBody: Partial<TAction>) {
|
||||
const handleObjectiveChange = (actionIdx: number, objective: TActionObjective) => {
|
||||
const action = actions[actionIdx];
|
||||
const actionBody = getUpdatedActionBody(action, updateActionBody);
|
||||
const parsedActionBodyResult = ZAction.safeParse(actionBody);
|
||||
if (!parsedActionBodyResult.success) {
|
||||
console.error("Failed to update action", parsedActionBodyResult.error.errors);
|
||||
return;
|
||||
}
|
||||
handleActionsChange("update", actionIdx, parsedActionBodyResult.data);
|
||||
}
|
||||
const actionBody = getUpdatedActionBody(action, objective);
|
||||
handleActionsChange("update", actionIdx, actionBody);
|
||||
};
|
||||
|
||||
const handleValuesChange = (actionIdx: number, values: Partial<TAction>) => {
|
||||
const action = actions[actionIdx];
|
||||
const actionBody = { ...action, ...values } as TAction;
|
||||
handleActionsChange("update", actionIdx, actionBody);
|
||||
};
|
||||
|
||||
console.log("actions", actions);
|
||||
return (
|
||||
<div className="flex grow gap-2">
|
||||
<CornerDownRightIcon className="mt-3 h-4 w-4 shrink-0" />
|
||||
@@ -95,29 +96,42 @@ export function AdvancedLogicEditorActions({
|
||||
options={actionObjectiveOptions}
|
||||
value={action.objective}
|
||||
onChangeValue={(val: TActionObjective) => {
|
||||
updateAction(idx, {
|
||||
objective: val,
|
||||
});
|
||||
handleObjectiveChange(idx, val);
|
||||
}}
|
||||
comboboxClasses="grow"
|
||||
/>
|
||||
<InputCombobox
|
||||
key="target"
|
||||
showSearch={false}
|
||||
options={
|
||||
action.objective === "calculate"
|
||||
? getActionVariableOptions(localSurvey)
|
||||
: getActionTargetOptions(action, localSurvey, questionIdx)
|
||||
}
|
||||
value={action.objective === "calculate" ? action.variableId : action.target}
|
||||
onChangeValue={(val: string) => {
|
||||
updateAction(idx, {
|
||||
...(action.objective === "calculate" ? { variableId: val } : { target: val }),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{action.objective !== "calculate" && (
|
||||
<InputCombobox
|
||||
key="target"
|
||||
showSearch={false}
|
||||
options={getActionTargetOptions(action, localSurvey, questionIdx)}
|
||||
value={action.target}
|
||||
onChangeValue={(val: string) => {
|
||||
handleValuesChange(idx, {
|
||||
target: val,
|
||||
});
|
||||
}}
|
||||
comboboxClasses="w-40"
|
||||
/>
|
||||
)}
|
||||
{action.objective === "calculate" && (
|
||||
<>
|
||||
<InputCombobox
|
||||
key="variableId"
|
||||
showSearch={false}
|
||||
options={getActionVariableOptions(localSurvey)}
|
||||
value={action.variableId}
|
||||
onChangeValue={(val: string) => {
|
||||
handleValuesChange(idx, {
|
||||
variableId: val,
|
||||
value: {
|
||||
type: "static",
|
||||
value: "",
|
||||
},
|
||||
});
|
||||
}}
|
||||
comboboxClasses="w-40"
|
||||
/>
|
||||
<InputCombobox
|
||||
key="attribute"
|
||||
showSearch={false}
|
||||
@@ -128,10 +142,11 @@ export function AdvancedLogicEditorActions({
|
||||
onChangeValue={(
|
||||
val: TActionTextVariableCalculateOperator | TActionNumberVariableCalculateOperator
|
||||
) => {
|
||||
updateAction(idx, {
|
||||
handleValuesChange(idx, {
|
||||
operator: val,
|
||||
});
|
||||
}}
|
||||
comboboxClasses="w-20"
|
||||
/>
|
||||
<InputCombobox
|
||||
key="value"
|
||||
@@ -143,20 +158,20 @@ export function AdvancedLogicEditorActions({
|
||||
type: localSurvey.variables.find((v) => v.id === action.variableId)?.type || "text",
|
||||
}}
|
||||
groupedOptions={getActionValueOptions(action.variableId, localSurvey, questionIdx)}
|
||||
onChangeValue={(val: string | number, option) => {
|
||||
onChangeValue={(val, option, fromInput) => {
|
||||
const fieldType = option?.meta?.type as TActionVariableValueType;
|
||||
|
||||
if (fieldType !== "static") {
|
||||
updateAction(idx, {
|
||||
if (!fromInput && fieldType !== "static") {
|
||||
handleValuesChange(idx, {
|
||||
value: {
|
||||
type: fieldType,
|
||||
value: val as string,
|
||||
},
|
||||
});
|
||||
} else if (fieldType === "static") {
|
||||
updateAction(idx, {
|
||||
} else if (fromInput) {
|
||||
handleValuesChange(idx, {
|
||||
value: {
|
||||
type: fieldType,
|
||||
type: "static",
|
||||
value: val as string,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -365,6 +365,30 @@ export const ruleEngine = {
|
||||
label: "does not equal",
|
||||
value: ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
},
|
||||
{
|
||||
label: "contains",
|
||||
value: ZSurveyLogicCondition.Enum.contains,
|
||||
},
|
||||
{
|
||||
label: "does not contain",
|
||||
value: ZSurveyLogicCondition.Enum.doesNotContain,
|
||||
},
|
||||
{
|
||||
label: "starts with",
|
||||
value: ZSurveyLogicCondition.Enum.startsWith,
|
||||
},
|
||||
{
|
||||
label: "does not start with",
|
||||
value: ZSurveyLogicCondition.Enum.doesNotStartWith,
|
||||
},
|
||||
{
|
||||
label: "ends with",
|
||||
value: ZSurveyLogicCondition.Enum.endsWith,
|
||||
},
|
||||
{
|
||||
label: "does not end with",
|
||||
value: ZSurveyLogicCondition.Enum.doesNotEndWith,
|
||||
},
|
||||
],
|
||||
},
|
||||
number: {
|
||||
@@ -406,6 +430,30 @@ export const ruleEngine = {
|
||||
label: "does not equal",
|
||||
value: ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
},
|
||||
{
|
||||
label: "contains",
|
||||
value: ZSurveyLogicCondition.Enum.contains,
|
||||
},
|
||||
{
|
||||
label: "does not contain",
|
||||
value: ZSurveyLogicCondition.Enum.doesNotContain,
|
||||
},
|
||||
{
|
||||
label: "starts with",
|
||||
value: ZSurveyLogicCondition.Enum.startsWith,
|
||||
},
|
||||
{
|
||||
label: "does not start with",
|
||||
value: ZSurveyLogicCondition.Enum.doesNotStartWith,
|
||||
},
|
||||
{
|
||||
label: "ends with",
|
||||
value: ZSurveyLogicCondition.Enum.endsWith,
|
||||
},
|
||||
{
|
||||
label: "does not end with",
|
||||
value: ZSurveyLogicCondition.Enum.doesNotEndWith,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -725,15 +725,19 @@ export const getActionTargetOptions = (
|
||||
localSurvey: TSurvey,
|
||||
currQuestionIdx: number
|
||||
): TComboboxOption[] => {
|
||||
const questionOptions = localSurvey.questions
|
||||
.filter((_, idx) => idx !== currQuestionIdx)
|
||||
.map((question) => {
|
||||
return {
|
||||
icon: questionIconMapping[question.type],
|
||||
label: getLocalizedValue(question.headline, "default"),
|
||||
value: question.id,
|
||||
};
|
||||
});
|
||||
let questions = localSurvey.questions.filter((_, idx) => idx !== currQuestionIdx);
|
||||
|
||||
if (action.objective === "requireAnswer") {
|
||||
questions = questions.filter((question) => !question.required);
|
||||
}
|
||||
|
||||
const questionOptions = questions.map((question) => {
|
||||
return {
|
||||
icon: questionIconMapping[question.type],
|
||||
label: getLocalizedValue(question.headline, "default"),
|
||||
value: question.id,
|
||||
};
|
||||
});
|
||||
|
||||
if (action.objective === "requireAnswer") return questionOptions;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import {
|
||||
TAction,
|
||||
TActionObjective,
|
||||
TConditionGroup,
|
||||
TSingleCondition,
|
||||
TSurveyAdvancedLogic,
|
||||
@@ -174,37 +175,28 @@ export const updateCondition = (
|
||||
}
|
||||
};
|
||||
|
||||
export const getUpdatedActionBody = (action: TAction, update: Partial<TAction>) => {
|
||||
if (update.objective) {
|
||||
if (update.objective === action.objective) return action;
|
||||
switch (update.objective) {
|
||||
case "calculate":
|
||||
return {
|
||||
...action,
|
||||
...update,
|
||||
objective: "calculate",
|
||||
variableId: "",
|
||||
operator: "assign",
|
||||
value: update.value ? { ...update.value } : { type: "static", value: "" },
|
||||
};
|
||||
case "requireAnswer":
|
||||
return {
|
||||
...action,
|
||||
...update,
|
||||
objective: "requireAnswer",
|
||||
target: "",
|
||||
};
|
||||
case "jumpToQuestion":
|
||||
return {
|
||||
...action,
|
||||
...update,
|
||||
objective: "jumpToQuestion",
|
||||
target: "",
|
||||
};
|
||||
}
|
||||
export const getUpdatedActionBody = (action: TAction, objective: TActionObjective): TAction => {
|
||||
if (objective === action.objective) return action;
|
||||
switch (objective) {
|
||||
case "calculate":
|
||||
return {
|
||||
id: action.id,
|
||||
objective: "calculate",
|
||||
variableId: "",
|
||||
operator: "assign",
|
||||
value: { type: "static", value: "" },
|
||||
};
|
||||
case "requireAnswer":
|
||||
return {
|
||||
id: action.id,
|
||||
objective: "requireAnswer",
|
||||
target: "",
|
||||
};
|
||||
case "jumpToQuestion":
|
||||
return {
|
||||
id: action.id,
|
||||
objective: "jumpToQuestion",
|
||||
target: "",
|
||||
};
|
||||
}
|
||||
return {
|
||||
...action,
|
||||
...update,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,291 +1,5 @@
|
||||
import { z } from "zod";
|
||||
|
||||
// export const ZSurveyOpenTextQuestionInputType = z.enum(["text", "email", "url", "number", "phone"]);
|
||||
|
||||
// export enum TSurveyQuestionTypeEnum {
|
||||
// FileUpload = "fileUpload",
|
||||
// OpenText = "openText",
|
||||
// MultipleChoiceSingle = "multipleChoiceSingle",
|
||||
// MultipleChoiceMulti = "multipleChoiceMulti",
|
||||
// NPS = "nps",
|
||||
// CTA = "cta",
|
||||
// Rating = "rating",
|
||||
// Consent = "consent",
|
||||
// PictureSelection = "pictureSelection",
|
||||
// Cal = "cal",
|
||||
// Date = "date",
|
||||
// Matrix = "matrix",
|
||||
// Address = "address",
|
||||
// }
|
||||
|
||||
// export type TSurveyLogicCondition = z.infer<typeof ZSurveyLogicCondition>;
|
||||
|
||||
// export type TDyanmicLogicField = z.infer<typeof ZDyanmicLogicField>;
|
||||
|
||||
// const ZMatchValueBase = z.object({
|
||||
// type: z.enum(["static", "dynamic"]),
|
||||
// });
|
||||
|
||||
// const ZMatchValueStatic = ZMatchValueBase.extend({
|
||||
// type: z.literal("static"),
|
||||
// value: z.union([z.string(), z.array(z.string()), z.number()]),
|
||||
// });
|
||||
|
||||
// const ZMatchValueDynamic = ZMatchValueBase.extend({
|
||||
// type: z.literal("dynamic"),
|
||||
// id: z.string(),
|
||||
// fieldType: ZDyanmicLogicField,
|
||||
// });
|
||||
|
||||
// const ZMatchValue = z.union([ZMatchValueStatic, ZMatchValueDynamic]);
|
||||
|
||||
// const ZLogicalConnector = z.enum(["and", "or"]);
|
||||
// export type TLogicalConnector = z.infer<typeof ZLogicalConnector>;
|
||||
|
||||
// const ZConditionBase = z.object({
|
||||
// id: z.string().cuid2(),
|
||||
// connector: ZLogicalConnector.nullable(),
|
||||
// type: ZDyanmicLogicField,
|
||||
// conditionValue: z.string(),
|
||||
// conditionOperator: ZSurveyLogicCondition.nullable(),
|
||||
// matchValue: ZMatchValue.nullable(),
|
||||
// });
|
||||
|
||||
// export type TConditionBase = z.infer<typeof ZConditionBase>;
|
||||
|
||||
// const ZConditionQuestionBase = ZConditionBase.extend({
|
||||
// type: z.literal("question"),
|
||||
// questionType: z.nativeEnum(TSurveyQuestionTypeEnum),
|
||||
// });
|
||||
|
||||
// export type TConditionQuestionBase = z.infer<typeof ZConditionQuestionBase>;
|
||||
|
||||
// const ZOpenTextConditionBase = ZConditionQuestionBase.extend({
|
||||
// questionType: z.literal(TSurveyQuestionTypeEnum.OpenText),
|
||||
// inputType: ZSurveyOpenTextQuestionInputType.optional().default("text"),
|
||||
// });
|
||||
|
||||
// const ZOpenTextStringConditon = ZOpenTextConditionBase.extend({
|
||||
// inputType: z.enum(["text", "email", "url", "phone"]),
|
||||
// conditionOperator: z.enum([
|
||||
// "equals",
|
||||
// "doesNotEqual",
|
||||
// "contains",
|
||||
// "doesNotContain",
|
||||
// "startsWith",
|
||||
// "doesNotStartWith",
|
||||
// "endsWith",
|
||||
// "doesNotEndWith",
|
||||
// "isSubmitted",
|
||||
// "isSkipped",
|
||||
// ]),
|
||||
// matchValue: z.string().optional(),
|
||||
// });
|
||||
|
||||
// const ZOpenTextNumberConditon = ZOpenTextConditionBase.extend({
|
||||
// inputType: z.literal("number"),
|
||||
// conditionOperator: z.enum([
|
||||
// "equals",
|
||||
// "doesNotEqual",
|
||||
// "isGreaterThan",
|
||||
// "isLessThan",
|
||||
// "isGreaterThanOrEqual",
|
||||
// "isLessThanOrEqual",
|
||||
// "isSubmitted",
|
||||
// "isSkipped",
|
||||
// ]),
|
||||
// matchValue: z.number().optional(),
|
||||
// });
|
||||
|
||||
// const ZOpenTextCondition = z.union([ZOpenTextStringConditon, ZOpenTextNumberConditon]);
|
||||
|
||||
// const ZMultipleChoiceSingleCondition = ZConditionQuestionBase.extend({
|
||||
// questionType: z.literal(TSurveyQuestionTypeEnum.MultipleChoiceSingle),
|
||||
// conditionOperator: z.enum(["equals", "doesNotEqual", "equalsOneOf", "isSubmitted", "isSkipped"]),
|
||||
// });
|
||||
|
||||
// const ZMultipleChoiceMultiCondition = ZConditionQuestionBase.extend({
|
||||
// questionType: z.literal(TSurveyQuestionTypeEnum.MultipleChoiceMulti),
|
||||
// conditionOperator: z.enum([
|
||||
// "equals",
|
||||
// "doesNotEqual",
|
||||
// "includesAllOf",
|
||||
// "includesOneOf",
|
||||
// "isSubmitted",
|
||||
// "isSkipped",
|
||||
// ]),
|
||||
// });
|
||||
|
||||
// const ZPictureSelectionCondition = ZConditionQuestionBase.extend({
|
||||
// questionType: z.literal(TSurveyQuestionTypeEnum.PictureSelection),
|
||||
// conditionOperator: z.enum([
|
||||
// "equals",
|
||||
// "doesNotEqual",
|
||||
// "includesAllOf",
|
||||
// "includesOneOf",
|
||||
// "isSubmitted",
|
||||
// "isSkipped",
|
||||
// ]),
|
||||
// });
|
||||
|
||||
// const ZRatingCondition = ZConditionQuestionBase.extend({
|
||||
// questionType: z.literal(TSurveyQuestionTypeEnum.Rating),
|
||||
// conditionOperator: z.enum([
|
||||
// "equals",
|
||||
// "doesNotEqual",
|
||||
// "isGreaterThan",
|
||||
// "isLessThan",
|
||||
// "isGreaterThanOrEqual",
|
||||
// "isLessThanOrEqual",
|
||||
// "isSubmitted",
|
||||
// "isSkipped",
|
||||
// ]),
|
||||
// matchValue: z.number().optional(),
|
||||
// });
|
||||
|
||||
// const ZNPSCondition = ZConditionQuestionBase.extend({
|
||||
// questionType: z.literal(TSurveyQuestionTypeEnum.NPS),
|
||||
// conditionOperator: z.enum([
|
||||
// "equals",
|
||||
// "doesNotEqual",
|
||||
// "isGreaterThan",
|
||||
// "isLessThan",
|
||||
// "isGreaterThanOrEqual",
|
||||
// "isLessThanOrEqual",
|
||||
// "isSubmitted",
|
||||
// "isSkipped",
|
||||
// ]),
|
||||
// matchValue: z.number().optional(),
|
||||
// });
|
||||
|
||||
// const ZCTACondition = ZConditionQuestionBase.extend({
|
||||
// questionType: z.literal(TSurveyQuestionTypeEnum.CTA),
|
||||
// conditionOperator: z.enum(["isClicked", "isSkipped"]),
|
||||
// matchValue: z.undefined(),
|
||||
// });
|
||||
|
||||
// const ZConsentCondition = ZConditionQuestionBase.extend({
|
||||
// questionType: z.literal(TSurveyQuestionTypeEnum.Consent),
|
||||
// conditionOperator: z.enum(["isAccepted", "isSkipped"]),
|
||||
// matchValue: z.undefined(),
|
||||
// });
|
||||
|
||||
// const ZDateCondition = ZConditionQuestionBase.extend({
|
||||
// questionType: z.literal(TSurveyQuestionTypeEnum.Date),
|
||||
// conditionOperator: z.enum(["equals", "doesNotEqual", "isBefore", "isAfter", "isSubmitted", "isSkipped"]),
|
||||
// matchValue: z.string().optional(),
|
||||
// });
|
||||
|
||||
// const ZFileUploadCondition = ZConditionQuestionBase.extend({
|
||||
// questionType: z.literal(TSurveyQuestionTypeEnum.FileUpload),
|
||||
// conditionOperator: z.enum(["isSubmitted", "isSkipped"]),
|
||||
// matchValue: z.undefined(),
|
||||
// });
|
||||
|
||||
// const ZCalCondition = ZConditionQuestionBase.extend({
|
||||
// questionType: z.literal(TSurveyQuestionTypeEnum.Cal),
|
||||
// conditionOperator: z.enum(["isBooked", "isSkipped"]),
|
||||
// matchValue: z.undefined(),
|
||||
// });
|
||||
|
||||
// const ZMatrixCondition = ZConditionQuestionBase.extend({
|
||||
// questionType: z.literal(TSurveyQuestionTypeEnum.Matrix),
|
||||
// conditionOperator: z.enum(["isPartiallySubmitted", "isCompletelySubmitted", "isSkipped"]),
|
||||
// matchValue: z.undefined(),
|
||||
// });
|
||||
|
||||
// const ZAddressCondition = ZConditionQuestionBase.extend({
|
||||
// questionType: z.literal(TSurveyQuestionTypeEnum.Address),
|
||||
// conditionOperator: z.enum(["isSubmitted", "isSkipped"]),
|
||||
// matchValue: z.undefined(),
|
||||
// });
|
||||
|
||||
// const ZConditionQuestion = z.union([
|
||||
// ZOpenTextCondition,
|
||||
// ZMultipleChoiceSingleCondition,
|
||||
// ZMultipleChoiceMultiCondition,
|
||||
// ZPictureSelectionCondition,
|
||||
// ZRatingCondition,
|
||||
// ZNPSCondition,
|
||||
// ZCTACondition,
|
||||
// ZConsentCondition,
|
||||
// ZDateCondition,
|
||||
// ZFileUploadCondition,
|
||||
// ZCalCondition,
|
||||
// ZMatrixCondition,
|
||||
// ZAddressCondition,
|
||||
// ]);
|
||||
|
||||
// const ZConditionVariableBase = ZConditionBase.extend({
|
||||
// type: z.literal("variable"),
|
||||
// variableType: z.enum(["number", "text"]),
|
||||
// });
|
||||
|
||||
// const ZConditionTextVariable = ZConditionVariableBase.extend({
|
||||
// variableType: z.literal("text"),
|
||||
// conditionOperator: z.enum([
|
||||
// "equals",
|
||||
// "doesNotEqual",
|
||||
// "contains",
|
||||
// "doesNotContain",
|
||||
// "startsWith",
|
||||
// "doesNotStartWith",
|
||||
// "endsWith",
|
||||
// "doesNotEndWith",
|
||||
// ]),
|
||||
// });
|
||||
|
||||
// const ZConditionNumberVariable = ZConditionVariableBase.extend({
|
||||
// variableType: z.literal("number"),
|
||||
// conditionOperator: z.enum([
|
||||
// "equals",
|
||||
// "doesNotEqual",
|
||||
// "isGreaterThan",
|
||||
// "isLessThan",
|
||||
// "isGreaterThanOrEqual",
|
||||
// "isLessThanOrEqual",
|
||||
// ]),
|
||||
// });
|
||||
|
||||
// const ZConditionVariable = z.union([ZConditionTextVariable, ZConditionNumberVariable]);
|
||||
|
||||
// const ZConditionAttributeClass = ZConditionBase.extend({
|
||||
// type: z.literal("attributeClass"),
|
||||
// conditionOperator: z.enum([
|
||||
// "equals",
|
||||
// "doesNotEqual",
|
||||
// "contains",
|
||||
// "doesNotContain",
|
||||
// "startsWith",
|
||||
// "doesNotStartWith",
|
||||
// "endsWith",
|
||||
// "doesNotEndWith",
|
||||
// ]),
|
||||
// });
|
||||
|
||||
// const ZConditionHiddenField = ZConditionBase.extend({
|
||||
// type: z.literal("hiddenField"),
|
||||
// conditionOperator: z.enum([
|
||||
// "equals",
|
||||
// "doesNotEqual",
|
||||
// "contains",
|
||||
// "doesNotContain",
|
||||
// "startsWith",
|
||||
// "doesNotStartWith",
|
||||
// "endsWith",
|
||||
// "doesNotEndWith",
|
||||
// ]),
|
||||
// });
|
||||
|
||||
// const ZCondition = z.union([
|
||||
// ZConditionQuestion,
|
||||
// ZConditionVariable,
|
||||
// ZConditionAttributeClass,
|
||||
// ZConditionHiddenField,
|
||||
// ]);
|
||||
|
||||
// export type TCondition = z.infer<typeof ZCondition>;
|
||||
|
||||
export const ZSurveyLogicCondition = z.enum([
|
||||
"equals",
|
||||
"doesNotEqual",
|
||||
@@ -315,31 +29,35 @@ export const ZSurveyLogicCondition = z.enum([
|
||||
|
||||
export const ZDyanmicLogicField = z.enum(["question", "variable", "hiddenField"]);
|
||||
export const ZActionObjective = z.enum(["calculate", "requireAnswer", "jumpToQuestion"]);
|
||||
export const ZActionTextVariableCalculateOperator = z.enum(["assign", "concat"]);
|
||||
export const ZActionNumberVariableCalculateOperator = z.enum([
|
||||
"add",
|
||||
"subtract",
|
||||
"multiply",
|
||||
"divide",
|
||||
"assign",
|
||||
]);
|
||||
export const ZActionTextVariableCalculateOperator = z.enum(["assign", "concat"], {
|
||||
message: "Invalid operator for a text variable",
|
||||
});
|
||||
export const ZActionNumberVariableCalculateOperator = z.enum(
|
||||
["add", "subtract", "multiply", "divide", "assign"],
|
||||
{ message: "Invalid operator for a number variable" }
|
||||
);
|
||||
|
||||
const ZDynamicQuestion = z.object({
|
||||
type: z.literal("question"),
|
||||
value: z.string(),
|
||||
value: z.string().min(1, "Question id cannot be empty"),
|
||||
});
|
||||
|
||||
const ZDynamicVariable = z.object({
|
||||
type: z.literal("variable"),
|
||||
value: z.string().cuid2(),
|
||||
value: z
|
||||
.string()
|
||||
.cuid2({ message: "Variable id must be a valid cuid" })
|
||||
.min(1, "Variable id cannot be empty"),
|
||||
});
|
||||
|
||||
const ZDynamicHiddenField = z.object({
|
||||
type: z.literal("hiddenField"),
|
||||
value: z.string(),
|
||||
value: z.string().min(1, "Hidden field id cannot be empty"),
|
||||
});
|
||||
|
||||
const ZDynamicLogicFieldValue = z.union([ZDynamicQuestion, ZDynamicVariable, ZDynamicHiddenField]);
|
||||
const ZDynamicLogicFieldValue = z.union([ZDynamicQuestion, ZDynamicVariable, ZDynamicHiddenField], {
|
||||
message: "Invalid dynamic field value",
|
||||
});
|
||||
|
||||
export type TSurveyLogicCondition = z.infer<typeof ZSurveyLogicCondition>;
|
||||
export type TDyanmicLogicField = z.infer<typeof ZDyanmicLogicField>;
|
||||
@@ -404,23 +122,25 @@ const ZActionCalculateBase = ZActionBase.extend({
|
||||
variableId: z.string(),
|
||||
});
|
||||
|
||||
const ZActionCalculateText = ZActionCalculateBase.extend({
|
||||
export const ZActionCalculateText = ZActionCalculateBase.extend({
|
||||
operator: ZActionTextVariableCalculateOperator,
|
||||
value: z.union([
|
||||
z.object({
|
||||
type: z.literal("static"),
|
||||
value: z.string(),
|
||||
value: z
|
||||
.string({ message: "Value must be a string for text variable" })
|
||||
.min(1, "please enter a value in logic field"),
|
||||
}),
|
||||
ZDynamicLogicFieldValue,
|
||||
]),
|
||||
});
|
||||
|
||||
const ZActionCalculateNumber = ZActionCalculateBase.extend({
|
||||
export const ZActionCalculateNumber = ZActionCalculateBase.extend({
|
||||
operator: ZActionNumberVariableCalculateOperator,
|
||||
value: z.union([
|
||||
z.object({
|
||||
type: z.literal("static"),
|
||||
value: z.number(),
|
||||
value: z.number({ message: "Value must be a number for number variable" }),
|
||||
}),
|
||||
ZDynamicLogicFieldValue,
|
||||
]),
|
||||
|
||||
@@ -5,11 +5,21 @@ import { ZAllowedFileExtension, ZColor, ZId, ZPlacement } from "../common";
|
||||
import { ZLanguage } from "../product";
|
||||
import { ZSegment } from "../segment";
|
||||
import { ZBaseStyling } from "../styling";
|
||||
import { ZSurveyAdvancedLogic } from "./logic";
|
||||
import {
|
||||
type TAction,
|
||||
type TConditionGroup,
|
||||
type TSingleCondition,
|
||||
type TSurveyAdvancedLogic,
|
||||
type TSurveyLogicCondition,
|
||||
ZActionCalculateNumber,
|
||||
ZActionCalculateText,
|
||||
ZSurveyAdvancedLogic,
|
||||
} from "./logic";
|
||||
import {
|
||||
FORBIDDEN_IDS,
|
||||
findLanguageCodesForDuplicateLabels,
|
||||
findQuestionsWithCyclicLogic,
|
||||
isConditionsGroup,
|
||||
validateCardFieldsForAllLanguages,
|
||||
validateQuestionLabels,
|
||||
} from "./validation";
|
||||
@@ -280,22 +290,6 @@ export const ZSurveyMultipleChoiceQuestion = ZSurveyQuestionBase.extend({
|
||||
shuffleOption: ZShuffleOption.optional(),
|
||||
otherOptionPlaceholder: ZI18nString.optional(),
|
||||
});
|
||||
// .refine(
|
||||
// (question) => {
|
||||
// const { logic, type } = question;
|
||||
|
||||
// if (type === TSurveyQuestionTypeEnum.MultipleChoiceSingle) {
|
||||
// // The single choice question should not have 'includesAll' logic
|
||||
// return !logic?.some((l) => l.condition === "includesAll");
|
||||
// }
|
||||
// // The multi choice question should not have 'notEquals' logic
|
||||
// return !logic?.some((l) => l.condition === "notEquals");
|
||||
// },
|
||||
// {
|
||||
// message:
|
||||
// "MultipleChoiceSingle question should not have 'includesAll' logic and MultipleChoiceMulti question should not have 'notEquals' logic",
|
||||
// }
|
||||
// );
|
||||
|
||||
export type TSurveyMultipleChoiceQuestion = z.infer<typeof ZSurveyMultipleChoiceQuestion>;
|
||||
|
||||
@@ -827,32 +821,13 @@ export const ZSurvey = z
|
||||
}
|
||||
}
|
||||
|
||||
// if (question.logic) {
|
||||
// question.logic.forEach((logic, logicIndex) => {
|
||||
// // validate condition
|
||||
// // validate actions
|
||||
if (question.logic) {
|
||||
const logicIssues = validateLogic(survey, questionIndex, question.logic);
|
||||
|
||||
// // if (
|
||||
// // [
|
||||
// // "isSubmitted",
|
||||
// // "isSkipped",
|
||||
// // "isClicked",
|
||||
// // "isAccepted",
|
||||
// // "isBooked",
|
||||
// // "isPartiallySubmitted",
|
||||
// // "isCompletelySubmitted",
|
||||
// // ].includes(condition.operator) &&
|
||||
// // condition.rightOperand !== undefined
|
||||
// // ) {
|
||||
// // ctx.addIssue({
|
||||
// // code: z.ZodIssueCode.custom,
|
||||
// // message: `${messagePrefix}${messageField} in question ${String(questionIndex + 1)}${messageSuffix}`,
|
||||
// // path: ["questions", questionIndex, field],
|
||||
// // params: isDefaultOnly ? undefined : { invalidLanguageCodes },
|
||||
// // })
|
||||
// // }
|
||||
// });
|
||||
// }
|
||||
logicIssues.forEach((issue) => {
|
||||
ctx.addIssue(issue);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const questionsWithCyclicLogic = findQuestionsWithCyclicLogic(questions);
|
||||
@@ -921,9 +896,391 @@ export const ZSurvey = z
|
||||
});
|
||||
});
|
||||
|
||||
// const validateActions = (actions: TAction[], ctx: z.RefinementCtx) => {
|
||||
// // check if
|
||||
// };
|
||||
const isInvalidOperatorsForQuestionType = (
|
||||
question: TSurveyQuestion,
|
||||
operator: TSurveyLogicCondition
|
||||
): boolean => {
|
||||
let isInvalidOperator = false;
|
||||
|
||||
const questionType = question.type;
|
||||
|
||||
switch (questionType) {
|
||||
case TSurveyQuestionTypeEnum.OpenText:
|
||||
switch (question.inputType) {
|
||||
case "email":
|
||||
case "phone":
|
||||
case "text":
|
||||
case "url":
|
||||
if (
|
||||
![
|
||||
"equals",
|
||||
"doesNotEqual",
|
||||
"contains",
|
||||
"doesNotContain",
|
||||
"startsWith",
|
||||
"doesNotStartWith",
|
||||
"endsWith",
|
||||
"doesNotEndWith",
|
||||
"isSubmitted",
|
||||
"isSkipped",
|
||||
].includes(operator)
|
||||
) {
|
||||
isInvalidOperator = true;
|
||||
}
|
||||
break;
|
||||
case "number":
|
||||
if (
|
||||
![
|
||||
"equals",
|
||||
"doesNotEqual",
|
||||
"isGreaterThan",
|
||||
"isLessThan",
|
||||
"isGreaterThanOrEqual",
|
||||
"isLessThanOrEqual",
|
||||
"isSubmitted",
|
||||
"isSkipped",
|
||||
].includes(operator)
|
||||
) {
|
||||
isInvalidOperator = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TSurveyQuestionTypeEnum.MultipleChoiceSingle:
|
||||
if (!["equals", "doesNotEqual", "equalsOneOf", "isSubmitted", "isSkipped"].includes(operator)) {
|
||||
isInvalidOperator = true;
|
||||
}
|
||||
break;
|
||||
case TSurveyQuestionTypeEnum.MultipleChoiceMulti:
|
||||
case TSurveyQuestionTypeEnum.PictureSelection:
|
||||
if (
|
||||
!["equals", "doesNotEqual", "includesAllOf", "includesOneOf", "isSubmitted", "isSkipped"].includes(
|
||||
operator
|
||||
)
|
||||
) {
|
||||
isInvalidOperator = true;
|
||||
}
|
||||
break;
|
||||
case TSurveyQuestionTypeEnum.NPS:
|
||||
case TSurveyQuestionTypeEnum.Rating:
|
||||
if (
|
||||
![
|
||||
"equals",
|
||||
"doesNotEqual",
|
||||
"isGreaterThan",
|
||||
"isLessThan",
|
||||
"isGreaterThanOrEqual",
|
||||
"isLessThanOrEqual",
|
||||
"isSubmitted",
|
||||
"isSkipped",
|
||||
].includes(operator)
|
||||
) {
|
||||
isInvalidOperator = true;
|
||||
}
|
||||
break;
|
||||
case TSurveyQuestionTypeEnum.CTA:
|
||||
if (!["isClicked", "isSkipped"].includes(operator)) {
|
||||
isInvalidOperator = true;
|
||||
}
|
||||
break;
|
||||
case TSurveyQuestionTypeEnum.Consent:
|
||||
if (!["isAccepted", "isSkipped"].includes(operator)) {
|
||||
isInvalidOperator = true;
|
||||
}
|
||||
break;
|
||||
case TSurveyQuestionTypeEnum.Date:
|
||||
if (!["equals", "doesNotEqual", "isBefore", "isAfter", "isSubmitted", "isSkipped"].includes(operator)) {
|
||||
isInvalidOperator = true;
|
||||
}
|
||||
break;
|
||||
case TSurveyQuestionTypeEnum.FileUpload:
|
||||
case TSurveyQuestionTypeEnum.Address:
|
||||
if (!["isSubmitted", "isSkipped"].includes(operator)) {
|
||||
isInvalidOperator = true;
|
||||
}
|
||||
break;
|
||||
case TSurveyQuestionTypeEnum.Cal:
|
||||
if (!["isBooked", "isSkipped"].includes(operator)) {
|
||||
isInvalidOperator = true;
|
||||
}
|
||||
break;
|
||||
case TSurveyQuestionTypeEnum.Matrix:
|
||||
if (!["isPartiallySubmitted", "isCompletelySubmitted", "isSkipped"].includes(operator)) {
|
||||
isInvalidOperator = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
isInvalidOperator = true;
|
||||
}
|
||||
|
||||
return isInvalidOperator;
|
||||
};
|
||||
|
||||
const isInvalidOperatorsForVariableType = (
|
||||
variableType: "text" | "number",
|
||||
operator: TSurveyLogicCondition
|
||||
): boolean => {
|
||||
let isInvalidOperator = false;
|
||||
|
||||
switch (variableType) {
|
||||
case "text":
|
||||
if (
|
||||
![
|
||||
"equals",
|
||||
"doesNotEqual",
|
||||
"contains",
|
||||
"doesNotContain",
|
||||
"startsWith",
|
||||
"doesNotStartWith",
|
||||
"endsWith",
|
||||
"doesNotEndWith",
|
||||
].includes(operator)
|
||||
) {
|
||||
isInvalidOperator = true;
|
||||
}
|
||||
break;
|
||||
case "number":
|
||||
if (
|
||||
![
|
||||
"equals",
|
||||
"doesNotEqual",
|
||||
"isGreaterThan",
|
||||
"isLessThan",
|
||||
"isGreaterThanOrEqual",
|
||||
"isLessThanOrEqual",
|
||||
].includes(operator)
|
||||
) {
|
||||
isInvalidOperator = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return isInvalidOperator;
|
||||
};
|
||||
|
||||
const isInvalidOperatorsForHiddenFieldType = (operator: TSurveyLogicCondition): boolean => {
|
||||
let isInvalidOperator = false;
|
||||
|
||||
if (
|
||||
![
|
||||
"equals",
|
||||
"doesNotEqual",
|
||||
"contains",
|
||||
"doesNotContain",
|
||||
"startsWith",
|
||||
"doesNotStartWith",
|
||||
"endsWith",
|
||||
"doesNotEndWith",
|
||||
].includes(operator)
|
||||
) {
|
||||
isInvalidOperator = true;
|
||||
}
|
||||
|
||||
return isInvalidOperator;
|
||||
};
|
||||
|
||||
const validateConditions = (
|
||||
survey: TSurvey,
|
||||
questionIndex: number,
|
||||
logicIndex: number,
|
||||
conditions: TConditionGroup
|
||||
): z.ZodIssue[] => {
|
||||
const issues: z.ZodIssue[] = [];
|
||||
|
||||
const validateSingleCondition = (condition: TSingleCondition): void => {
|
||||
const { leftOperand, operator, rightOperand } = condition;
|
||||
|
||||
// Validate left operand
|
||||
if (leftOperand.type === "question") {
|
||||
const questionId = leftOperand.value;
|
||||
const question = survey.questions.find((q) => q.id === questionId);
|
||||
if (!question) {
|
||||
issues.push({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `Question ID ${questionId} does not exist in logic no: ${String(logicIndex + 1)} of question ${String(questionIndex + 1)}`,
|
||||
path: ["questions", questionIndex, "logic", logicIndex, "conditions"],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate operator based on question type
|
||||
const isInvalidOperator = isInvalidOperatorsForQuestionType(question, operator);
|
||||
if (isInvalidOperator) {
|
||||
issues.push({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `Invalid operator "${operator}" for question type "${question.type}" in logic no: ${String(logicIndex + 1)} of question ${String(questionIndex + 1)}`,
|
||||
path: ["questions", questionIndex, "logic", logicIndex, "conditions"],
|
||||
});
|
||||
}
|
||||
|
||||
// Validate right operand
|
||||
if (
|
||||
[
|
||||
"isSubmitted",
|
||||
"isSkipped",
|
||||
"isClicked",
|
||||
"isAccepted",
|
||||
"isBooked",
|
||||
"isPartiallySubmitted",
|
||||
"isCompletelySubmitted",
|
||||
].includes(operator) &&
|
||||
rightOperand !== undefined
|
||||
) {
|
||||
issues.push({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `Right operand should not be defined for operator "${operator}" in logic no: ${String(logicIndex + 1)} of question ${String(questionIndex + 1)}`,
|
||||
path: ["questions", questionIndex, "logic", logicIndex, "conditions"],
|
||||
});
|
||||
}
|
||||
} else if (leftOperand.type === "variable") {
|
||||
const variableId = leftOperand.value;
|
||||
const variable = survey.variables.find((v) => v.id === variableId);
|
||||
if (!variable) {
|
||||
issues.push({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `Variable ID ${variableId} does not exist in logic no: ${String(logicIndex + 1)} of question ${String(questionIndex + 1)}`,
|
||||
path: ["questions", questionIndex, "logic", logicIndex, "conditions"],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate operator based on variable type
|
||||
const isInvalidOperator = isInvalidOperatorsForVariableType(variable.type, operator);
|
||||
if (isInvalidOperator) {
|
||||
issues.push({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `Invalid operator "${operator}" for variable ${variable.name} of type "${variable.type}" in logic no: ${String(logicIndex + 1)} of question ${String(questionIndex + 1)}`,
|
||||
path: ["questions", questionIndex, "logic", logicIndex, "conditions"],
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const hiddenFieldId = leftOperand.value;
|
||||
const hiddenField = survey.hiddenFields.fieldIds?.find((fieldId) => fieldId === hiddenFieldId);
|
||||
|
||||
if (!hiddenField) {
|
||||
issues.push({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `Hidden field ID ${hiddenFieldId} does not exist in logic no: ${String(logicIndex + 1)} of question ${String(questionIndex + 1)}`,
|
||||
path: ["questions", questionIndex, "logic", logicIndex, "conditions"],
|
||||
});
|
||||
}
|
||||
|
||||
// Validate operator based on hidden field type
|
||||
const isInvalidOperator = isInvalidOperatorsForHiddenFieldType(operator);
|
||||
if (isInvalidOperator) {
|
||||
issues.push({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `Invalid operator "${operator}" for hidden field in logic no: ${String(logicIndex + 1)} of question ${String(questionIndex + 1)}`,
|
||||
path: ["questions", questionIndex, "logic", logicIndex, "conditions"],
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const validateConditionGroup = (group: TConditionGroup): void => {
|
||||
group.conditions.forEach((condition) => {
|
||||
if (isConditionsGroup(condition)) {
|
||||
validateConditionGroup(condition);
|
||||
} else {
|
||||
validateSingleCondition(condition);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
validateConditionGroup(conditions);
|
||||
|
||||
return issues;
|
||||
};
|
||||
|
||||
const validateActions = (
|
||||
survey: TSurvey,
|
||||
questionIndex: number,
|
||||
logicIndex: number,
|
||||
actions: TAction[]
|
||||
): z.ZodIssue[] => {
|
||||
const questionIds = survey.questions.map((q) => q.id);
|
||||
|
||||
const actionIssues: (z.ZodIssue | undefined)[] = actions.map((action) => {
|
||||
if (action.objective === "calculate") {
|
||||
const variable = survey.variables.find((v) => v.id === action.variableId);
|
||||
|
||||
if (!variable) {
|
||||
return {
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `Variable ID ${action.variableId} does not exist in logic no: ${String(logicIndex + 1)} of question ${String(questionIndex + 1)}`,
|
||||
path: ["questions", questionIndex, "logic", logicIndex],
|
||||
};
|
||||
}
|
||||
|
||||
if (variable.type === "text") {
|
||||
const textVariableParseData = ZActionCalculateText.safeParse(action);
|
||||
if (!textVariableParseData.success) {
|
||||
return {
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: textVariableParseData.error.errors[0].message,
|
||||
path: ["questions", questionIndex, "logic", logicIndex],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const numberVariableParseData = ZActionCalculateNumber.safeParse(action);
|
||||
if (!numberVariableParseData.success) {
|
||||
return {
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: numberVariableParseData.error.errors[0].message,
|
||||
path: ["questions", questionIndex, "logic", logicIndex],
|
||||
};
|
||||
}
|
||||
} else {
|
||||
const endingIds = survey.endings.map((ending) => ending.id);
|
||||
|
||||
const possibleQuestionIds =
|
||||
action.objective === "jumpToQuestion" ? [...questionIds, ...endingIds] : questionIds;
|
||||
|
||||
if (!possibleQuestionIds.includes(action.target)) {
|
||||
return {
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `Question ID ${action.target} does not exist in logic no: ${String(logicIndex + 1)} of question ${String(questionIndex + 1)}`,
|
||||
path: ["questions", questionIndex, "logic"],
|
||||
};
|
||||
}
|
||||
|
||||
if (action.objective === "requireAnswer") {
|
||||
const requiredQuestionIds = survey.questions
|
||||
.filter((question) => question.required)
|
||||
.map((question) => question.id);
|
||||
|
||||
if (!requiredQuestionIds.includes(action.target)) {
|
||||
const quesIdx = survey.questions.findIndex((q) => q.id === action.target);
|
||||
|
||||
return {
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `Question ${String(quesIdx + 1)} is already required in logic no: ${String(logicIndex + 1)} of question ${String(questionIndex + 1)}`,
|
||||
path: ["questions", questionIndex, "logic", logicIndex],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
|
||||
return actionIssues.filter((issue) => issue !== undefined);
|
||||
};
|
||||
|
||||
const validateLogic = (
|
||||
survey: TSurvey,
|
||||
questionIndex: number,
|
||||
logic: TSurveyAdvancedLogic[]
|
||||
): z.ZodIssue[] => {
|
||||
const logicIssues = logic.map((logicItem, logicIndex) => {
|
||||
return [
|
||||
...validateConditions(survey, questionIndex, logicIndex, logicItem.conditions),
|
||||
...validateActions(survey, questionIndex, logicIndex, logicItem.actions),
|
||||
];
|
||||
});
|
||||
|
||||
return logicIssues.flat();
|
||||
};
|
||||
|
||||
// ZSurvey is a refinement, so to extend it to ZSurveyUpdateInput, we need to transform the innerType and then apply the same refinements.
|
||||
export const ZSurveyUpdateInput = ZSurvey.innerType()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import type { TAction, TActionJumpToQuestion } from "./logic";
|
||||
import type { TAction, TActionJumpToQuestion, TConditionGroup, TSingleCondition } from "./logic";
|
||||
import type { TI18nString, TSurveyLanguage, TSurveyQuestion } from "./types";
|
||||
|
||||
export const FORBIDDEN_IDS = [
|
||||
@@ -219,3 +219,9 @@ export const validateId = (
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
type TCondition = TSingleCondition | TConditionGroup;
|
||||
|
||||
export const isConditionsGroup = (condition: TCondition): condition is TConditionGroup => {
|
||||
return "conditions" in condition;
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@ interface InputComboboxProps {
|
||||
options?: TComboboxOption[];
|
||||
groupedOptions?: TComboboxGroupedOption[];
|
||||
value?: string | number | string[] | null;
|
||||
onChangeValue: (value: string | number | string[], option?: TComboboxOption) => void;
|
||||
onChangeValue: (value: string | number | string[], option?: TComboboxOption, fromInput?: boolean) => void;
|
||||
inputProps?: Omit<React.ComponentProps<typeof Input>, "value" | "onChange">;
|
||||
clearable?: boolean;
|
||||
withInput?: boolean;
|
||||
@@ -92,6 +92,9 @@ export const InputCombobox = ({
|
||||
if (inputType !== "input") {
|
||||
setInputType("input");
|
||||
}
|
||||
} else {
|
||||
setLocalValue(null);
|
||||
setInputType(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,7 +131,9 @@ export const InputCombobox = ({
|
||||
};
|
||||
|
||||
const onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const inputType = e.target.type;
|
||||
const value = e.target.value;
|
||||
|
||||
if (value === "") {
|
||||
setLocalValue(null);
|
||||
onChangeValue("");
|
||||
@@ -138,8 +143,10 @@ export const InputCombobox = ({
|
||||
setInputType("input");
|
||||
}
|
||||
|
||||
setLocalValue(value);
|
||||
onChangeValue(value);
|
||||
const val = inputType === "number" ? Number(value) : value;
|
||||
|
||||
setLocalValue(val);
|
||||
onChangeValue(val, undefined, true);
|
||||
};
|
||||
|
||||
const getDisplayValue = useMemo(() => {
|
||||
|
||||
Reference in New Issue
Block a user