mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 18:30:32 -06:00
adds local schema updation handlers
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { AdvancedLogicEditorActions } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedLogicEditorActions";
|
||||
import { AdvancedLogicEditorConditions } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedLogicEditorConditions";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { removeAction } from "@formbricks/lib/survey/logic/utils";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TSurveyAdvancedLogic } from "@formbricks/types/surveys/logic";
|
||||
import { TAction, TSurveyAdvancedLogic } from "@formbricks/types/surveys/logic";
|
||||
import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys/types";
|
||||
|
||||
interface AdvancedLogicEditorProps {
|
||||
@@ -12,9 +12,7 @@ interface AdvancedLogicEditorProps {
|
||||
question: TSurveyQuestion;
|
||||
questionIdx: number;
|
||||
logicIdx: number;
|
||||
hiddenFields: string[];
|
||||
userAttributes: string[];
|
||||
attributeClasses: TAttributeClass[];
|
||||
}
|
||||
|
||||
export function AdvancedLogicEditor({
|
||||
@@ -24,20 +22,27 @@ export function AdvancedLogicEditor({
|
||||
question,
|
||||
questionIdx,
|
||||
logicIdx,
|
||||
hiddenFields,
|
||||
userAttributes,
|
||||
attributeClasses,
|
||||
}: AdvancedLogicEditorProps) {
|
||||
const handleActionsChange = (action: "delete" | "addBelow" | "duplicate", actionIdx: number) => {
|
||||
const handleActionsChange = (
|
||||
operation: "delete" | "addBelow" | "duplicate" | "update",
|
||||
actionIdx: number,
|
||||
action?: Partial<TAction>
|
||||
) => {
|
||||
const actionsClone = structuredClone(logicItem.actions);
|
||||
let updatedActions: TSurveyAdvancedLogic["actions"] = actionsClone;
|
||||
|
||||
if (action === "delete") {
|
||||
if (operation === "delete") {
|
||||
updatedActions = removeAction(actionsClone, actionIdx);
|
||||
} else if (action === "addBelow") {
|
||||
updatedActions.splice(actionIdx + 1, 0, { objective: "" });
|
||||
} else if (action === "duplicate") {
|
||||
} else if (operation === "addBelow") {
|
||||
updatedActions.splice(actionIdx + 1, 0, { id: createId(), objective: "jumpToQuestion", target: "" });
|
||||
} else if (operation === "duplicate") {
|
||||
updatedActions.splice(actionIdx + 1, 0, actionsClone[actionIdx]);
|
||||
} else if (operation === "update") {
|
||||
updatedActions[actionIdx] = {
|
||||
...updatedActions[actionIdx],
|
||||
...action,
|
||||
};
|
||||
}
|
||||
|
||||
updateQuestion(questionIdx, {
|
||||
@@ -60,18 +65,16 @@ export function AdvancedLogicEditor({
|
||||
updateQuestion={updateQuestion}
|
||||
question={question}
|
||||
questionIdx={questionIdx}
|
||||
localSurvey={localSurvey}
|
||||
logicIdx={logicIdx}
|
||||
hiddenFields={hiddenFields}
|
||||
userAttributes={userAttributes}
|
||||
/>
|
||||
<AdvancedLogicEditorActions
|
||||
logicItem={logicItem}
|
||||
handleActionsChange={handleActionsChange}
|
||||
hiddenFields={hiddenFields}
|
||||
localSurvey={localSurvey}
|
||||
userAttributes={userAttributes}
|
||||
questionIdx={questionIdx}
|
||||
attributeClasses={attributeClasses}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import {
|
||||
getOpeartorOptions,
|
||||
getTargetOptions,
|
||||
getValueOptions,
|
||||
getActionOpeartorOptions,
|
||||
getActionTargetOptions,
|
||||
getActionValueOptions,
|
||||
getActionVariableOptions,
|
||||
} from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/util";
|
||||
import { CopyIcon, CornerDownRightIcon, MoreVerticalIcon, PlusIcon, Trash2Icon } from "lucide-react";
|
||||
import { useMemo } from "react";
|
||||
import { actionObjectiveOptions } from "@formbricks/lib/survey/logic/utils";
|
||||
import { replaceHeadlineRecall } from "@formbricks/lib/utils/recall";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TSurveyAdvancedLogic } from "@formbricks/types/surveys/logic";
|
||||
import {
|
||||
TAction,
|
||||
TActionCalculateVariableType,
|
||||
TActionNumberVariableCalculateOperator,
|
||||
TActionObjective,
|
||||
TActionTextVariableCalculateOperator,
|
||||
TDyanmicLogicField,
|
||||
TSurveyAdvancedLogic,
|
||||
} from "@formbricks/types/surveys/logic";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -21,27 +27,29 @@ import { InputCombobox } from "@formbricks/ui/InputCombobox";
|
||||
interface AdvancedLogicEditorActions {
|
||||
localSurvey: TSurvey;
|
||||
logicItem: TSurveyAdvancedLogic;
|
||||
handleActionsChange: (action: "delete" | "addBelow" | "duplicate", actionIdx: number) => void;
|
||||
hiddenFields: string[];
|
||||
handleActionsChange: (
|
||||
operation: "delete" | "addBelow" | "duplicate" | "update",
|
||||
actionIdx: number,
|
||||
action?: Partial<TAction>
|
||||
) => void;
|
||||
userAttributes: string[];
|
||||
questionIdx: number;
|
||||
attributeClasses: TAttributeClass[];
|
||||
}
|
||||
|
||||
export function AdvancedLogicEditorActions({
|
||||
localSurvey,
|
||||
logicItem,
|
||||
handleActionsChange,
|
||||
hiddenFields,
|
||||
userAttributes,
|
||||
questionIdx,
|
||||
attributeClasses,
|
||||
}: AdvancedLogicEditorActions) {
|
||||
const actions = logicItem.actions;
|
||||
const transformedSurvey = useMemo(() => {
|
||||
return replaceHeadlineRecall(localSurvey, "default", attributeClasses);
|
||||
}, [localSurvey, attributeClasses]);
|
||||
|
||||
const updateAction = (actionIdx: number, updatedAction: Partial<TAction>) => {
|
||||
handleActionsChange("update", actionIdx, updatedAction);
|
||||
};
|
||||
|
||||
console.log("actions", actions);
|
||||
return (
|
||||
<div className="">
|
||||
<div className="flex gap-2">
|
||||
@@ -56,38 +64,82 @@ export function AdvancedLogicEditorActions({
|
||||
showSearch={false}
|
||||
options={actionObjectiveOptions}
|
||||
selected={action.objective}
|
||||
onChangeValue={() => {}}
|
||||
onChangeValue={(val: TActionObjective) => {
|
||||
updateAction(idx, {
|
||||
objective: val,
|
||||
target: "",
|
||||
operator: undefined,
|
||||
variableType: undefined,
|
||||
});
|
||||
}}
|
||||
comboboxClasses="max-w-[200px]"
|
||||
/>
|
||||
<InputCombobox
|
||||
key="target"
|
||||
showSearch={false}
|
||||
options={getTargetOptions(transformedSurvey.questions, questionIdx)}
|
||||
selected={action.objective}
|
||||
onChangeValue={() => {}}
|
||||
comboboxClasses="grow"
|
||||
options={
|
||||
action.objective === "calculate"
|
||||
? getActionVariableOptions(localSurvey)
|
||||
: getActionTargetOptions(localSurvey, questionIdx)
|
||||
}
|
||||
selected={action.target}
|
||||
onChangeValue={(val: string, option) => {
|
||||
updateAction(idx, {
|
||||
target: val,
|
||||
variableType: option?.meta?.variableType as TActionCalculateVariableType,
|
||||
});
|
||||
}}
|
||||
comboboxClasses="grow min-w-[100px]"
|
||||
/>
|
||||
{action.objective === "calculate" && (
|
||||
<>
|
||||
<InputCombobox
|
||||
key="attribute"
|
||||
showSearch={false}
|
||||
options={getOpeartorOptions(action.variableType)}
|
||||
options={getActionOpeartorOptions(action.variableType)}
|
||||
selected={action.operator}
|
||||
onChangeValue={() => {}}
|
||||
onChangeValue={(
|
||||
val: TActionNumberVariableCalculateOperator | TActionTextVariableCalculateOperator
|
||||
) => {
|
||||
updateAction(idx, {
|
||||
operator: val,
|
||||
});
|
||||
}}
|
||||
comboboxClasses="min-w-[100px]"
|
||||
/>
|
||||
<InputCombobox
|
||||
key="value"
|
||||
withInput={true}
|
||||
inputProps={{ placeholder: "Value" }}
|
||||
groupedOptions={getValueOptions(
|
||||
transformedSurvey.questions,
|
||||
questionIdx,
|
||||
hiddenFields,
|
||||
userAttributes
|
||||
)}
|
||||
onChangeValue={() => {}}
|
||||
comboboxClasses="flex"
|
||||
inputProps={{
|
||||
placeholder: "Value",
|
||||
value: typeof action.value !== "object" ? action.value : "",
|
||||
type: action.variableType,
|
||||
onChange: (e) => {
|
||||
let val: string | number = e.target.value;
|
||||
|
||||
if (action.variableType === "number") {
|
||||
val = Number(val);
|
||||
updateAction(idx, {
|
||||
value: val,
|
||||
});
|
||||
} else if (action.variableType === "text") {
|
||||
updateAction(idx, {
|
||||
value: val,
|
||||
});
|
||||
}
|
||||
},
|
||||
}}
|
||||
groupedOptions={getActionValueOptions(localSurvey, questionIdx, userAttributes)}
|
||||
onChangeValue={(val: string, option) => {
|
||||
updateAction(idx, {
|
||||
value: {
|
||||
id: val,
|
||||
fieldType: option?.meta?.fieldType as TDyanmicLogicField,
|
||||
type: "dynamic",
|
||||
},
|
||||
});
|
||||
}}
|
||||
comboboxClasses="flex min-w-[100px]"
|
||||
comboboxSize="sm"
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
import {
|
||||
getConditionOperatorOptions,
|
||||
getConditionValueOptions,
|
||||
getMatchValueProps,
|
||||
} from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/util";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { CopyIcon, MoreVerticalIcon, PlusIcon, Trash2Icon, WorkflowIcon } from "lucide-react";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { performOperationsOnConditions } from "@formbricks/lib/survey/logic/utils";
|
||||
import { TConditionBase, TSurveyAdvancedLogic } from "@formbricks/types/surveys/logic";
|
||||
import { TSurveyQuestion } from "@formbricks/types/surveys/types";
|
||||
import { TConditionBase, TSurveyAdvancedLogic, TSurveyLogicCondition } from "@formbricks/types/surveys/logic";
|
||||
import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys/types";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@formbricks/ui/DropdownMenu";
|
||||
import { Select, SelectContent, SelectTrigger } from "@formbricks/ui/Select";
|
||||
import { InputCombobox } from "@formbricks/ui/InputCombobox";
|
||||
|
||||
interface AdvancedLogicEditorConditions {
|
||||
conditions: TSurveyAdvancedLogic["conditions"];
|
||||
updateQuestion: (questionIdx: number, updatedAttributes: any) => void;
|
||||
question: TSurveyQuestion;
|
||||
localSurvey: TSurvey;
|
||||
questionIdx: number;
|
||||
logicIdx: number;
|
||||
hiddenFields: string[];
|
||||
userAttributes: string[];
|
||||
}
|
||||
|
||||
@@ -26,15 +31,21 @@ export function AdvancedLogicEditorConditions({
|
||||
conditions,
|
||||
logicIdx,
|
||||
question,
|
||||
localSurvey,
|
||||
questionIdx,
|
||||
updateQuestion,
|
||||
hiddenFields,
|
||||
userAttributes,
|
||||
}: AdvancedLogicEditorConditions) {
|
||||
const handleAddConditionBelow = (resourceId: string, condition: TConditionBase) => {
|
||||
const advancedLogicCopy = structuredClone(question.advancedLogic);
|
||||
const handleAddConditionBelow = (resourceId: string, condition: Partial<TConditionBase>) => {
|
||||
const advancedLogicCopy = structuredClone(question.advancedLogic) || [];
|
||||
|
||||
performOperationsOnConditions("addConditionBelow", advancedLogicCopy, logicIdx, resourceId, condition);
|
||||
performOperationsOnConditions({
|
||||
action: "addConditionBelow",
|
||||
advancedLogicCopy,
|
||||
logicIdx,
|
||||
resourceId,
|
||||
condition,
|
||||
});
|
||||
|
||||
updateQuestion(questionIdx, {
|
||||
advancedLogic: advancedLogicCopy,
|
||||
@@ -43,10 +54,14 @@ export function AdvancedLogicEditorConditions({
|
||||
|
||||
const handleConnectorChange = (resourceId: string, connector: TConditionBase["connector"]) => {
|
||||
if (!connector) return;
|
||||
console.log("onConnectorChange", resourceId, connector);
|
||||
const advancedLogicCopy = structuredClone(question.advancedLogic);
|
||||
const advancedLogicCopy = structuredClone(question.advancedLogic) || [];
|
||||
|
||||
performOperationsOnConditions("toggleConnector", advancedLogicCopy, logicIdx, resourceId, connector);
|
||||
performOperationsOnConditions({
|
||||
action: "toggleConnector",
|
||||
advancedLogicCopy,
|
||||
logicIdx,
|
||||
resourceId,
|
||||
});
|
||||
|
||||
updateQuestion(questionIdx, {
|
||||
advancedLogic: advancedLogicCopy,
|
||||
@@ -54,9 +69,14 @@ export function AdvancedLogicEditorConditions({
|
||||
};
|
||||
|
||||
const handleRemoveCondition = (resourceId: string) => {
|
||||
const advancedLogicCopy = structuredClone(question.advancedLogic);
|
||||
const advancedLogicCopy = structuredClone(question.advancedLogic) || [];
|
||||
|
||||
performOperationsOnConditions("removeCondition", advancedLogicCopy, logicIdx, resourceId);
|
||||
performOperationsOnConditions({
|
||||
action: "removeCondition",
|
||||
advancedLogicCopy,
|
||||
logicIdx,
|
||||
resourceId,
|
||||
});
|
||||
|
||||
updateQuestion(questionIdx, {
|
||||
advancedLogic: advancedLogicCopy,
|
||||
@@ -64,9 +84,9 @@ export function AdvancedLogicEditorConditions({
|
||||
};
|
||||
|
||||
const handleDuplicateCondition = (resourceId: string) => {
|
||||
const advancedLogicCopy = structuredClone(question.advancedLogic);
|
||||
const advancedLogicCopy = structuredClone(question.advancedLogic) || [];
|
||||
|
||||
performOperationsOnConditions("duplicateCondition", advancedLogicCopy, logicIdx, resourceId);
|
||||
performOperationsOnConditions({ action: "duplicateCondition", advancedLogicCopy, logicIdx, resourceId });
|
||||
|
||||
updateQuestion(questionIdx, {
|
||||
advancedLogic: advancedLogicCopy,
|
||||
@@ -74,9 +94,30 @@ export function AdvancedLogicEditorConditions({
|
||||
};
|
||||
|
||||
const handleCreateGroup = (resourceId: string) => {
|
||||
const advancedLogicCopy = structuredClone(question.advancedLogic);
|
||||
const advancedLogicCopy = structuredClone(question.advancedLogic) || [];
|
||||
|
||||
performOperationsOnConditions("createGroup", advancedLogicCopy, logicIdx, resourceId);
|
||||
performOperationsOnConditions({
|
||||
action: "createGroup",
|
||||
advancedLogicCopy,
|
||||
logicIdx,
|
||||
resourceId,
|
||||
});
|
||||
|
||||
updateQuestion(questionIdx, {
|
||||
advancedLogic: advancedLogicCopy,
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdateCondition = (resourceId: string, updateConditionBody: Partial<TConditionBase>) => {
|
||||
const advancedLogicCopy = structuredClone(question.advancedLogic) || [];
|
||||
|
||||
performOperationsOnConditions({
|
||||
action: "updateCondition",
|
||||
advancedLogicCopy,
|
||||
logicIdx,
|
||||
resourceId,
|
||||
conditionBody: updateConditionBody,
|
||||
});
|
||||
|
||||
updateQuestion(questionIdx, {
|
||||
advancedLogic: advancedLogicCopy,
|
||||
@@ -110,10 +151,10 @@ export function AdvancedLogicEditorConditions({
|
||||
conditions={condition.conditions}
|
||||
key={id}
|
||||
updateQuestion={updateQuestion}
|
||||
localSurvey={localSurvey}
|
||||
question={question}
|
||||
questionIdx={questionIdx}
|
||||
logicIdx={logicIdx}
|
||||
hiddenFields={hiddenFields}
|
||||
userAttributes={userAttributes}
|
||||
/>
|
||||
</div>
|
||||
@@ -140,6 +181,9 @@ export function AdvancedLogicEditorConditions({
|
||||
);
|
||||
}
|
||||
|
||||
const conditionValueOptions = getConditionValueOptions(localSurvey, questionIdx, userAttributes);
|
||||
const conditionOperatorOptions = getConditionOperatorOptions(condition);
|
||||
const { show, options } = getMatchValueProps(localSurvey, condition, questionIdx, userAttributes);
|
||||
return (
|
||||
<div key={id} className="flex items-center justify-between gap-4">
|
||||
<div className="flex items-start gap-2">
|
||||
@@ -154,10 +198,43 @@ export function AdvancedLogicEditorConditions({
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Select>
|
||||
<SelectTrigger></SelectTrigger>
|
||||
<SelectContent></SelectContent>
|
||||
</Select>
|
||||
<InputCombobox
|
||||
key="conditionValue"
|
||||
showSearch={false}
|
||||
groupedOptions={conditionValueOptions}
|
||||
selected={condition.conditionValue}
|
||||
onChangeValue={(val: string, option) => {
|
||||
handleUpdateCondition(id, {
|
||||
conditionValue: val,
|
||||
...option?.meta,
|
||||
});
|
||||
}}
|
||||
comboboxClasses="grow"
|
||||
/>
|
||||
<InputCombobox
|
||||
key="conditionOperator"
|
||||
showSearch={false}
|
||||
options={conditionOperatorOptions}
|
||||
selected={condition.conditionOperator}
|
||||
onChangeValue={(val: TSurveyLogicCondition, option) => {
|
||||
console.log("val", val, option);
|
||||
handleUpdateCondition(id, {
|
||||
conditionOperator: val,
|
||||
});
|
||||
}}
|
||||
comboboxClasses="grow"
|
||||
/>
|
||||
{show && options.length > 0 && (
|
||||
<InputCombobox
|
||||
withInput
|
||||
key="conditionMatchValue"
|
||||
showSearch={false}
|
||||
groupedOptions={options}
|
||||
comboboxSize="sm"
|
||||
selected={condition.conditionValue}
|
||||
onChangeValue={() => {}}
|
||||
/>
|
||||
)}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<MoreVerticalIcon className="h-4 w-4" />
|
||||
@@ -169,8 +246,10 @@ export function AdvancedLogicEditorConditions({
|
||||
onClick={() => {
|
||||
handleAddConditionBelow(id, {
|
||||
id: createId(),
|
||||
|
||||
connector: "and",
|
||||
conditionValue: localSurvey.questions[questionIdx].id,
|
||||
type: "question",
|
||||
questionType: localSurvey.questions[questionIdx].type,
|
||||
});
|
||||
}}>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
|
||||
@@ -10,7 +10,6 @@ interface AdvancedSettingsProps {
|
||||
localSurvey: TSurvey;
|
||||
updateQuestion: (questionIdx: number, updatedAttributes: any) => void;
|
||||
attributeClasses: TAttributeClass[];
|
||||
hiddenFields: string[];
|
||||
userAttributes: string[];
|
||||
}
|
||||
|
||||
@@ -20,7 +19,6 @@ export const AdvancedSettings = ({
|
||||
localSurvey,
|
||||
updateQuestion,
|
||||
attributeClasses,
|
||||
hiddenFields,
|
||||
userAttributes,
|
||||
}: AdvancedSettingsProps) => {
|
||||
return (
|
||||
@@ -39,7 +37,6 @@ export const AdvancedSettings = ({
|
||||
localSurvey={localSurvey}
|
||||
questionIdx={questionIdx}
|
||||
attributeClasses={attributeClasses}
|
||||
hiddenFields={hiddenFields}
|
||||
userAttributes={userAttributes}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { AdvancedLogicEditor } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedLogicEditor";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { ArrowRightIcon, SplitIcon, Trash2Icon } from "lucide-react";
|
||||
import { useMemo } from "react";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { replaceHeadlineRecall } from "@formbricks/lib/utils/recall";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TSurveyAdvancedLogic } from "@formbricks/types/surveys/logic";
|
||||
import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys/types";
|
||||
import { Button } from "@formbricks/ui/Button";
|
||||
import { Label } from "@formbricks/ui/Label";
|
||||
@@ -13,7 +16,6 @@ interface ConditionalLogicProps {
|
||||
question: TSurveyQuestion;
|
||||
updateQuestion: (questionIdx: number, updatedAttributes: any) => void;
|
||||
attributeClasses: TAttributeClass[];
|
||||
hiddenFields: string[];
|
||||
userAttributes: string[];
|
||||
}
|
||||
|
||||
@@ -34,12 +36,36 @@ export function ConditionalLogic({
|
||||
question,
|
||||
questionIdx,
|
||||
updateQuestion,
|
||||
hiddenFields,
|
||||
userAttributes,
|
||||
}: ConditionalLogicProps) {
|
||||
const transformedSurvey = useMemo(() => {
|
||||
return replaceHeadlineRecall(localSurvey, "default", attributeClasses);
|
||||
}, [localSurvey, attributeClasses]);
|
||||
|
||||
const addLogic = () => {
|
||||
const condition: TSurveyAdvancedLogic = {
|
||||
id: createId(),
|
||||
conditions: [
|
||||
{
|
||||
id: createId(),
|
||||
connector: null,
|
||||
type: "question",
|
||||
conditionValue: question.id,
|
||||
questionType: question.type,
|
||||
matchValue: null,
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
id: createId(),
|
||||
objective: "jumpToQuestion",
|
||||
target: "",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
updateQuestion(questionIdx, {
|
||||
advancedLogic: [...(question?.advancedLogic || []), initialLogicState],
|
||||
advancedLogic: [...(question?.advancedLogic || []), condition],
|
||||
});
|
||||
};
|
||||
|
||||
@@ -63,15 +89,13 @@ export function ConditionalLogic({
|
||||
{question.advancedLogic.map((logicItem, logicItemIdx) => (
|
||||
<div key={logicItem.id} className="flex items-start gap-2">
|
||||
<AdvancedLogicEditor
|
||||
localSurvey={localSurvey}
|
||||
localSurvey={transformedSurvey}
|
||||
logicItem={logicItem}
|
||||
updateQuestion={updateQuestion}
|
||||
question={question}
|
||||
questionIdx={questionIdx}
|
||||
logicIdx={logicItemIdx}
|
||||
userAttributes={userAttributes}
|
||||
hiddenFields={hiddenFields}
|
||||
attributeClasses={attributeClasses}
|
||||
/>
|
||||
<Button
|
||||
className="mt-1 p-0"
|
||||
|
||||
@@ -53,7 +53,6 @@ interface QuestionCardProps {
|
||||
attributeClasses: TAttributeClass[];
|
||||
addQuestion: (question: any, index?: number) => void;
|
||||
isFormbricksCloud: boolean;
|
||||
hiddenFields: string[];
|
||||
userAttributes: string[];
|
||||
}
|
||||
|
||||
@@ -75,7 +74,6 @@ export const QuestionCard = ({
|
||||
attributeClasses,
|
||||
addQuestion,
|
||||
isFormbricksCloud,
|
||||
hiddenFields,
|
||||
userAttributes,
|
||||
}: QuestionCardProps) => {
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||
@@ -459,7 +457,6 @@ export const QuestionCard = ({
|
||||
localSurvey={localSurvey}
|
||||
updateQuestion={updateQuestion}
|
||||
attributeClasses={attributeClasses}
|
||||
hiddenFields={hiddenFields}
|
||||
userAttributes={userAttributes}
|
||||
/>
|
||||
</Collapsible.CollapsibleContent>
|
||||
|
||||
@@ -20,7 +20,6 @@ interface QuestionsDraggableProps {
|
||||
attributeClasses: TAttributeClass[];
|
||||
addQuestion: (question: any, index?: number) => void;
|
||||
isFormbricksCloud: boolean;
|
||||
hiddenFields: string[];
|
||||
userAttributes: string[];
|
||||
}
|
||||
|
||||
@@ -40,7 +39,6 @@ export const QuestionsDroppable = ({
|
||||
attributeClasses,
|
||||
addQuestion,
|
||||
isFormbricksCloud,
|
||||
hiddenFields,
|
||||
userAttributes,
|
||||
}: QuestionsDraggableProps) => {
|
||||
return (
|
||||
@@ -66,7 +64,6 @@ export const QuestionsDroppable = ({
|
||||
attributeClasses={attributeClasses}
|
||||
addQuestion={addQuestion}
|
||||
isFormbricksCloud={isFormbricksCloud}
|
||||
hiddenFields={hiddenFields}
|
||||
userAttributes={userAttributes}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { AddEndingCardButton } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AddEndingCardButton";
|
||||
import { SurveyVariablesCard } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyVariablesCard";
|
||||
import {
|
||||
DndContext,
|
||||
DragEndEvent,
|
||||
@@ -49,7 +50,6 @@ interface QuestionsViewProps {
|
||||
isFormbricksCloud: boolean;
|
||||
attributeClasses: TAttributeClass[];
|
||||
plan: TOrganizationBillingPlan;
|
||||
hiddenFields: string[];
|
||||
userAttributes: string[];
|
||||
}
|
||||
|
||||
@@ -67,7 +67,6 @@ export const QuestionsView = ({
|
||||
isFormbricksCloud,
|
||||
attributeClasses,
|
||||
plan,
|
||||
hiddenFields,
|
||||
userAttributes,
|
||||
}: QuestionsViewProps) => {
|
||||
const internalQuestionIdMap = useMemo(() => {
|
||||
@@ -393,7 +392,6 @@ export const QuestionsView = ({
|
||||
attributeClasses={attributeClasses}
|
||||
addQuestion={addQuestion}
|
||||
isFormbricksCloud={isFormbricksCloud}
|
||||
hiddenFields={hiddenFields}
|
||||
userAttributes={userAttributes}
|
||||
/>
|
||||
</DndContext>
|
||||
@@ -439,12 +437,12 @@ export const QuestionsView = ({
|
||||
activeQuestionId={activeQuestionId}
|
||||
/>
|
||||
|
||||
{/* <SurveyVariablesCard
|
||||
<SurveyVariablesCard
|
||||
localSurvey={localSurvey}
|
||||
setLocalSurvey={setLocalSurvey}
|
||||
activeQuestionId={activeQuestionId}
|
||||
setActiveQuestionId={setActiveQuestionId}
|
||||
/> */}
|
||||
/>
|
||||
|
||||
<MultiLanguageCard
|
||||
localSurvey={localSurvey}
|
||||
|
||||
@@ -37,7 +37,6 @@ interface SurveyEditorProps {
|
||||
isFormbricksCloud: boolean;
|
||||
isUnsplashConfigured: boolean;
|
||||
plan: TOrganizationBillingPlan;
|
||||
hiddenFields: string[];
|
||||
userAttributes: string[];
|
||||
}
|
||||
|
||||
@@ -57,7 +56,6 @@ export const SurveyEditor = ({
|
||||
isFormbricksCloud,
|
||||
isUnsplashConfigured,
|
||||
plan,
|
||||
hiddenFields,
|
||||
userAttributes,
|
||||
}: SurveyEditorProps) => {
|
||||
const [activeView, setActiveView] = useState<TSurveyEditorTabs>("questions");
|
||||
@@ -174,7 +172,6 @@ export const SurveyEditor = ({
|
||||
isFormbricksCloud={isFormbricksCloud}
|
||||
attributeClasses={attributeClasses}
|
||||
plan={plan}
|
||||
hiddenFields={hiddenFields}
|
||||
userAttributes={userAttributes}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,410 @@
|
||||
import { TSurveyQuestionTypeEnum, ZSurveyLogicCondition } from "@formbricks/types/surveys/logic";
|
||||
|
||||
export const ruleEngine = {
|
||||
question: {
|
||||
[TSurveyQuestionTypeEnum.OpenText]: {
|
||||
text: {
|
||||
options: [
|
||||
{
|
||||
label: "equals",
|
||||
value: ZSurveyLogicCondition.Enum.equals,
|
||||
},
|
||||
{
|
||||
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,
|
||||
},
|
||||
{
|
||||
label: "is submitted",
|
||||
value: ZSurveyLogicCondition.Enum.isSubmitted,
|
||||
},
|
||||
{
|
||||
label: "is skipped",
|
||||
value: ZSurveyLogicCondition.Enum.isSkipped,
|
||||
},
|
||||
],
|
||||
},
|
||||
number: {
|
||||
options: [
|
||||
{
|
||||
label: "=",
|
||||
value: ZSurveyLogicCondition.Enum.equals,
|
||||
},
|
||||
{
|
||||
label: "!=",
|
||||
value: ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
},
|
||||
{
|
||||
label: ">",
|
||||
value: ZSurveyLogicCondition.Enum.isGreaterThan,
|
||||
},
|
||||
{
|
||||
label: "<",
|
||||
value: ZSurveyLogicCondition.Enum.isLessThan,
|
||||
},
|
||||
{
|
||||
label: ">=",
|
||||
value: ZSurveyLogicCondition.Enum.isGreaterThanOrEqual,
|
||||
},
|
||||
{
|
||||
label: "<=",
|
||||
value: ZSurveyLogicCondition.Enum.isLessThanOrEqual,
|
||||
},
|
||||
{
|
||||
label: "is submitted",
|
||||
value: ZSurveyLogicCondition.Enum.isSubmitted,
|
||||
},
|
||||
{
|
||||
label: "is skipped",
|
||||
value: ZSurveyLogicCondition.Enum.isSkipped,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
[TSurveyQuestionTypeEnum.MultipleChoiceSingle]: {
|
||||
options: [
|
||||
{
|
||||
label: "equals",
|
||||
value: ZSurveyLogicCondition.Enum.equals,
|
||||
},
|
||||
{
|
||||
label: "does not equal",
|
||||
value: ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
},
|
||||
{
|
||||
label: "equals one of",
|
||||
value: ZSurveyLogicCondition.Enum.equalsOneOf,
|
||||
},
|
||||
{
|
||||
label: "is submitted",
|
||||
value: ZSurveyLogicCondition.Enum.isSubmitted,
|
||||
},
|
||||
{
|
||||
label: "is skipped",
|
||||
value: ZSurveyLogicCondition.Enum.isSkipped,
|
||||
},
|
||||
],
|
||||
},
|
||||
[TSurveyQuestionTypeEnum.MultipleChoiceMulti]: {
|
||||
options: [
|
||||
{
|
||||
label: "equals",
|
||||
value: ZSurveyLogicCondition.Enum.equals,
|
||||
},
|
||||
{
|
||||
label: "does not equal",
|
||||
value: ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
},
|
||||
{
|
||||
label: "includes all of",
|
||||
value: ZSurveyLogicCondition.Enum.includesAllOf,
|
||||
},
|
||||
{
|
||||
label: "includes one of",
|
||||
value: ZSurveyLogicCondition.Enum.includesOneOf,
|
||||
},
|
||||
{
|
||||
label: "is submitted",
|
||||
value: ZSurveyLogicCondition.Enum.isSubmitted,
|
||||
},
|
||||
{
|
||||
label: "is skipped",
|
||||
value: ZSurveyLogicCondition.Enum.isSkipped,
|
||||
},
|
||||
],
|
||||
},
|
||||
[TSurveyQuestionTypeEnum.PictureSelection]: {
|
||||
options: [
|
||||
{
|
||||
label: "equals",
|
||||
value: ZSurveyLogicCondition.Enum.equals,
|
||||
},
|
||||
{
|
||||
label: "does not equal",
|
||||
value: ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
},
|
||||
{
|
||||
label: "includes all of",
|
||||
value: ZSurveyLogicCondition.Enum.includesAllOf,
|
||||
},
|
||||
{
|
||||
label: "includes one of",
|
||||
value: ZSurveyLogicCondition.Enum.includesOneOf,
|
||||
},
|
||||
{
|
||||
label: "is submitted",
|
||||
value: ZSurveyLogicCondition.Enum.isSubmitted,
|
||||
},
|
||||
{
|
||||
label: "is skipped",
|
||||
value: ZSurveyLogicCondition.Enum.isSkipped,
|
||||
},
|
||||
],
|
||||
},
|
||||
[TSurveyQuestionTypeEnum.Rating]: {
|
||||
options: [
|
||||
{
|
||||
label: "=",
|
||||
value: ZSurveyLogicCondition.Enum.equals,
|
||||
},
|
||||
{
|
||||
label: "!=",
|
||||
value: ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
},
|
||||
{
|
||||
label: ">",
|
||||
value: ZSurveyLogicCondition.Enum.isGreaterThan,
|
||||
},
|
||||
{
|
||||
label: "<",
|
||||
value: ZSurveyLogicCondition.Enum.isLessThan,
|
||||
},
|
||||
{
|
||||
label: ">=",
|
||||
value: ZSurveyLogicCondition.Enum.isGreaterThanOrEqual,
|
||||
},
|
||||
{
|
||||
label: "<=",
|
||||
value: ZSurveyLogicCondition.Enum.isLessThanOrEqual,
|
||||
},
|
||||
{
|
||||
label: "is submitted",
|
||||
value: ZSurveyLogicCondition.Enum.isSubmitted,
|
||||
},
|
||||
{
|
||||
label: "is skipped",
|
||||
value: ZSurveyLogicCondition.Enum.isSkipped,
|
||||
},
|
||||
],
|
||||
},
|
||||
[TSurveyQuestionTypeEnum.NPS]: {
|
||||
options: [
|
||||
{
|
||||
label: "=",
|
||||
value: ZSurveyLogicCondition.Enum.equals,
|
||||
},
|
||||
{
|
||||
label: "!=",
|
||||
value: ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
},
|
||||
{
|
||||
label: ">",
|
||||
value: ZSurveyLogicCondition.Enum.isGreaterThan,
|
||||
},
|
||||
{
|
||||
label: "<",
|
||||
value: ZSurveyLogicCondition.Enum.isLessThan,
|
||||
},
|
||||
{
|
||||
label: ">=",
|
||||
value: ZSurveyLogicCondition.Enum.isGreaterThanOrEqual,
|
||||
},
|
||||
{
|
||||
label: "<=",
|
||||
value: ZSurveyLogicCondition.Enum.isLessThanOrEqual,
|
||||
},
|
||||
{
|
||||
label: "is submitted",
|
||||
value: ZSurveyLogicCondition.Enum.isSubmitted,
|
||||
},
|
||||
{
|
||||
label: "is skipped",
|
||||
value: ZSurveyLogicCondition.Enum.isSkipped,
|
||||
},
|
||||
],
|
||||
},
|
||||
[TSurveyQuestionTypeEnum.CTA]: {
|
||||
options: [
|
||||
{
|
||||
label: "is submitted",
|
||||
value: ZSurveyLogicCondition.Enum.isSubmitted,
|
||||
},
|
||||
{
|
||||
label: "is skipped",
|
||||
value: ZSurveyLogicCondition.Enum.isSkipped,
|
||||
},
|
||||
],
|
||||
},
|
||||
[TSurveyQuestionTypeEnum.Consent]: {
|
||||
options: [
|
||||
{
|
||||
label: "is accepted",
|
||||
value: ZSurveyLogicCondition.Enum.isAccepted,
|
||||
},
|
||||
{
|
||||
label: "is skipped",
|
||||
value: ZSurveyLogicCondition.Enum.isSkipped,
|
||||
},
|
||||
],
|
||||
},
|
||||
[TSurveyQuestionTypeEnum.Date]: {
|
||||
options: [
|
||||
{
|
||||
label: "equals",
|
||||
value: ZSurveyLogicCondition.Enum.equals,
|
||||
},
|
||||
{
|
||||
label: "does not equal",
|
||||
value: ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
},
|
||||
{
|
||||
label: "is before",
|
||||
value: ZSurveyLogicCondition.Enum.isBefore,
|
||||
},
|
||||
{
|
||||
label: "is after",
|
||||
value: ZSurveyLogicCondition.Enum.isAfter,
|
||||
},
|
||||
{
|
||||
label: "is submitted",
|
||||
value: ZSurveyLogicCondition.Enum.isSubmitted,
|
||||
},
|
||||
{
|
||||
label: "is skipped",
|
||||
value: ZSurveyLogicCondition.Enum.isSkipped,
|
||||
},
|
||||
],
|
||||
},
|
||||
[TSurveyQuestionTypeEnum.FileUpload]: {
|
||||
options: [
|
||||
{
|
||||
label: "is submitted",
|
||||
value: ZSurveyLogicCondition.Enum.isSubmitted,
|
||||
},
|
||||
{
|
||||
label: "is skipped",
|
||||
value: ZSurveyLogicCondition.Enum.isSkipped,
|
||||
},
|
||||
],
|
||||
},
|
||||
[TSurveyQuestionTypeEnum.Cal]: {
|
||||
options: [
|
||||
{
|
||||
label: "is booked",
|
||||
value: ZSurveyLogicCondition.Enum.isBooked,
|
||||
},
|
||||
{
|
||||
label: "is skipped",
|
||||
value: ZSurveyLogicCondition.Enum.isSkipped,
|
||||
},
|
||||
],
|
||||
},
|
||||
[TSurveyQuestionTypeEnum.Matrix]: {
|
||||
options: [
|
||||
{
|
||||
label: "is partially submitted",
|
||||
value: ZSurveyLogicCondition.Enum.isPartiallySubmitted,
|
||||
},
|
||||
{
|
||||
label: "is completely submitted",
|
||||
value: ZSurveyLogicCondition.Enum.isCompletelySubmitted,
|
||||
},
|
||||
{
|
||||
label: "is skipped",
|
||||
value: ZSurveyLogicCondition.Enum.isSkipped,
|
||||
},
|
||||
],
|
||||
},
|
||||
[TSurveyQuestionTypeEnum.Address]: {
|
||||
options: [
|
||||
{
|
||||
label: "is submitted",
|
||||
value: ZSurveyLogicCondition.Enum.isSubmitted,
|
||||
},
|
||||
{
|
||||
label: "is skipped",
|
||||
value: ZSurveyLogicCondition.Enum.isSkipped,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
variable: {
|
||||
text: {
|
||||
options: [
|
||||
{
|
||||
label: "equals",
|
||||
value: ZSurveyLogicCondition.Enum.equals,
|
||||
},
|
||||
{
|
||||
label: "does not equal",
|
||||
value: ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
},
|
||||
],
|
||||
},
|
||||
number: {
|
||||
options: [
|
||||
{
|
||||
label: "=",
|
||||
value: ZSurveyLogicCondition.Enum.equals,
|
||||
},
|
||||
{
|
||||
label: "!=",
|
||||
value: ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
},
|
||||
{
|
||||
label: ">",
|
||||
value: ZSurveyLogicCondition.Enum.isGreaterThan,
|
||||
},
|
||||
{
|
||||
label: "<",
|
||||
value: ZSurveyLogicCondition.Enum.isLessThan,
|
||||
},
|
||||
{
|
||||
label: ">=",
|
||||
value: ZSurveyLogicCondition.Enum.isGreaterThanOrEqual,
|
||||
},
|
||||
{
|
||||
label: "<=",
|
||||
value: ZSurveyLogicCondition.Enum.isLessThanOrEqual,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
hiddenField: {
|
||||
options: [
|
||||
{
|
||||
label: "equals",
|
||||
value: ZSurveyLogicCondition.Enum.equals,
|
||||
},
|
||||
{
|
||||
label: "does not equal",
|
||||
value: ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
},
|
||||
],
|
||||
},
|
||||
userAttribute: {
|
||||
options: [
|
||||
{
|
||||
label: "equals",
|
||||
value: ZSurveyLogicCondition.Enum.equals,
|
||||
},
|
||||
{
|
||||
label: "does not equal",
|
||||
value: ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -1,7 +1,11 @@
|
||||
import { ruleEngine } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/logicRuleEngine";
|
||||
import {
|
||||
ArrowUpFromLineIcon,
|
||||
CalendarDaysIcon,
|
||||
CheckIcon,
|
||||
EyeOffIcon,
|
||||
FileDigitIcon,
|
||||
FileType2Icon,
|
||||
Grid3X3Icon,
|
||||
HomeIcon,
|
||||
ImageIcon,
|
||||
@@ -12,14 +16,17 @@ import {
|
||||
PresentationIcon,
|
||||
Rows3Icon,
|
||||
StarIcon,
|
||||
TagIcon,
|
||||
} from "lucide-react";
|
||||
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
|
||||
import {
|
||||
TActionCalculateVariableType,
|
||||
TActionNumberVariableCalculateOperator,
|
||||
TActionTextVariableCalculateOperator,
|
||||
TCondition,
|
||||
TSurveyQuestionTypeEnum,
|
||||
} from "@formbricks/types/surveys/logic";
|
||||
import { TSurveyQuestions } from "@formbricks/types/surveys/types";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { ComboboxGroupedOption, ComboboxOption } from "@formbricks/ui/InputCombobox";
|
||||
|
||||
// formats the text to highlight specific parts of the text with slashes
|
||||
@@ -57,8 +64,266 @@ const questionIconMapping = {
|
||||
address: HomeIcon,
|
||||
};
|
||||
|
||||
export const getTargetOptions = (questions: TSurveyQuestions, currQuestionIdx: number): ComboboxOption[] => {
|
||||
return questions
|
||||
export const getConditionValueOptions = (
|
||||
localSurvey: TSurvey,
|
||||
currQuestionIdx: number,
|
||||
userAttributes: string[]
|
||||
): ComboboxGroupedOption[] => {
|
||||
const hiddenFields = localSurvey.hiddenFields?.fieldIds || [];
|
||||
const variables = localSurvey.variables || [];
|
||||
const questions = localSurvey.questions;
|
||||
|
||||
const groupedOptions: ComboboxGroupedOption[] = [];
|
||||
const questionOptions = questions
|
||||
.filter((_, idx) => idx <= currQuestionIdx)
|
||||
.map((question) => {
|
||||
return {
|
||||
icon: questionIconMapping[question.type],
|
||||
label: getLocalizedValue(question.headline, "default"),
|
||||
value: question.id,
|
||||
meta: {
|
||||
type: "question",
|
||||
questionType: question.type,
|
||||
inputType: question.type === "openText" ? question.inputType : "",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const variableOptions = variables.map((variable) => {
|
||||
return {
|
||||
icon: variable.type === "number" ? FileDigitIcon : FileType2Icon,
|
||||
label: variable.name,
|
||||
value: variable.id,
|
||||
meta: {
|
||||
type: "variable",
|
||||
variableType: variable.type,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const hiddenFieldsOptions = hiddenFields.map((field) => {
|
||||
return {
|
||||
icon: EyeOffIcon,
|
||||
label: field,
|
||||
value: field,
|
||||
meta: {
|
||||
type: "hiddenField",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const userAttributesOptions = userAttributes.map((attribute) => {
|
||||
return {
|
||||
icon: TagIcon,
|
||||
label: attribute,
|
||||
value: attribute,
|
||||
meta: {
|
||||
type: "userAttribute",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
if (questionOptions.length > 0) {
|
||||
groupedOptions.push({
|
||||
label: "Questions",
|
||||
value: "questions",
|
||||
options: questionOptions,
|
||||
});
|
||||
}
|
||||
|
||||
if (variableOptions.length > 0) {
|
||||
groupedOptions.push({
|
||||
label: "Variables",
|
||||
value: "variables",
|
||||
options: variableOptions,
|
||||
});
|
||||
}
|
||||
|
||||
if (hiddenFieldsOptions.length > 0) {
|
||||
groupedOptions.push({
|
||||
label: "Hidden Fields",
|
||||
value: "hiddenFields",
|
||||
options: hiddenFieldsOptions,
|
||||
});
|
||||
}
|
||||
|
||||
if (userAttributesOptions.length > 0) {
|
||||
groupedOptions.push({
|
||||
label: "User Attributes",
|
||||
value: "userAttributes",
|
||||
options: userAttributesOptions,
|
||||
});
|
||||
}
|
||||
|
||||
return groupedOptions;
|
||||
};
|
||||
|
||||
export const getConditionOperatorOptions = (condition: TCondition): ComboboxOption[] => {
|
||||
if (condition.type === "attributeClass") {
|
||||
return ruleEngine.userAttribute.options;
|
||||
} else if (condition.type === "variable") {
|
||||
return ruleEngine.variable[condition.variableType].options;
|
||||
} else if (condition.type === "hiddenField") {
|
||||
return ruleEngine.hiddenField.options;
|
||||
} else if (condition.type === "question") {
|
||||
if (condition.questionType === "openText") {
|
||||
const inputType = condition.inputType === "number" ? "number" : "text";
|
||||
return ruleEngine.question.openText[inputType].options;
|
||||
}
|
||||
return ruleEngine.question[condition.questionType].options;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
export const getMatchValueProps = (
|
||||
localSurvey: TSurvey,
|
||||
condition: TCondition,
|
||||
questionIdx: number,
|
||||
userAttributes: string[]
|
||||
): { show: boolean; options: ComboboxGroupedOption[] } => {
|
||||
if (
|
||||
[
|
||||
"isAccepted",
|
||||
"isBooked",
|
||||
"isClicked",
|
||||
"isCompletelySubmitted",
|
||||
"isPartiallySubmitted",
|
||||
"isSkipped",
|
||||
"isSubmitted",
|
||||
].includes(condition.conditionOperator)
|
||||
) {
|
||||
return { show: false, options: [] };
|
||||
}
|
||||
|
||||
let questionOptions = localSurvey.questions
|
||||
.filter((_, idx) => idx !== questionIdx)
|
||||
.map((question) => {
|
||||
return {
|
||||
icon: questionIconMapping[question.type],
|
||||
label: getLocalizedValue(question.headline, "default"),
|
||||
value: question.id,
|
||||
meta: {
|
||||
fieldType: "question",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
let variableOptions = localSurvey.variables.map((variable) => {
|
||||
return {
|
||||
icon: variable.type === "number" ? FileDigitIcon : FileType2Icon,
|
||||
label: variable.name,
|
||||
value: variable.id,
|
||||
meta: {
|
||||
fieldType: "variable",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
let hiddenFieldsOptions =
|
||||
localSurvey?.hiddenFields?.fieldIds?.map((field) => {
|
||||
return {
|
||||
icon: EyeOffIcon,
|
||||
label: field,
|
||||
value: field,
|
||||
meta: {
|
||||
fieldType: "hiddenField",
|
||||
},
|
||||
};
|
||||
}) || [];
|
||||
|
||||
let userAttributesOptions = userAttributes.map((attribute) => {
|
||||
return {
|
||||
icon: TagIcon,
|
||||
label: attribute,
|
||||
value: attribute,
|
||||
meta: {
|
||||
fieldType: "userAttribute",
|
||||
},
|
||||
};
|
||||
});
|
||||
const groupedOptions: ComboboxGroupedOption[] = [];
|
||||
|
||||
if (condition.type === "hiddenField") {
|
||||
hiddenFieldsOptions = hiddenFieldsOptions?.filter((field) => field.value !== condition.conditionValue);
|
||||
} else if (condition.type === "variable") {
|
||||
variableOptions = variableOptions?.filter((variable) => variable.value !== condition.conditionValue);
|
||||
} else if (condition.type === "attributeClass") {
|
||||
userAttributesOptions = userAttributesOptions?.filter(
|
||||
(attribute) => attribute.value !== condition.conditionValue
|
||||
);
|
||||
} else if (condition.type === "question") {
|
||||
questionOptions = questionOptions?.filter((question) => question.value !== condition.conditionValue);
|
||||
|
||||
const question = localSurvey.questions.find((question) => question.id === condition.conditionValue);
|
||||
|
||||
let choices: ComboboxOption[] = [];
|
||||
if (
|
||||
question &&
|
||||
(question.type === TSurveyQuestionTypeEnum.MultipleChoiceSingle ||
|
||||
question.type === TSurveyQuestionTypeEnum.MultipleChoiceMulti)
|
||||
) {
|
||||
choices = question.choices.map((choice) => ({
|
||||
label: getLocalizedValue(choice.label, "default"),
|
||||
value: choice.id,
|
||||
}));
|
||||
}
|
||||
if (question && question.type === TSurveyQuestionTypeEnum.PictureSelection) {
|
||||
choices = question.choices.map((choice, idx) => ({
|
||||
label: choice.imageUrl.split("/").pop() || `Image ${idx + 1}`,
|
||||
value: choice.id,
|
||||
}));
|
||||
}
|
||||
|
||||
if (choices.length > 0) {
|
||||
groupedOptions.push({
|
||||
label: "Choices",
|
||||
value: "choices",
|
||||
options: choices,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (questionOptions.length > 0) {
|
||||
groupedOptions.push({
|
||||
label: "Questions",
|
||||
value: "questions",
|
||||
options: questionOptions,
|
||||
});
|
||||
}
|
||||
|
||||
if (variableOptions.length > 0) {
|
||||
groupedOptions.push({
|
||||
label: "Variables",
|
||||
value: "variables",
|
||||
options: variableOptions,
|
||||
});
|
||||
}
|
||||
|
||||
if (hiddenFieldsOptions.length > 0) {
|
||||
groupedOptions.push({
|
||||
label: "Hidden Fields",
|
||||
value: "hiddenFields",
|
||||
options: hiddenFieldsOptions,
|
||||
});
|
||||
}
|
||||
|
||||
if (userAttributesOptions.length > 0) {
|
||||
groupedOptions.push({
|
||||
label: "User Attributes",
|
||||
value: "userAttributes",
|
||||
options: userAttributesOptions,
|
||||
});
|
||||
}
|
||||
|
||||
return { show: true, options: groupedOptions };
|
||||
|
||||
// const question = localSurvey.questions[questionIdx];
|
||||
|
||||
return { show: true, options: [] };
|
||||
};
|
||||
|
||||
export const getActionTargetOptions = (localSurvey: TSurvey, currQuestionIdx: number): ComboboxOption[] => {
|
||||
const questionOptions = localSurvey.questions
|
||||
.filter((_, idx) => idx !== currQuestionIdx)
|
||||
.map((question) => {
|
||||
return {
|
||||
@@ -67,9 +332,36 @@ export const getTargetOptions = (questions: TSurveyQuestions, currQuestionIdx: n
|
||||
value: question.id,
|
||||
};
|
||||
});
|
||||
|
||||
const endingCardOptions = localSurvey.endings.map((ending) => {
|
||||
return {
|
||||
label:
|
||||
ending.type === "endScreen"
|
||||
? `🙏${getLocalizedValue(ending.headline, "default")}`
|
||||
: `🙏 ${ending.label || "Redirect Thank you card"}`,
|
||||
value: ending.id,
|
||||
};
|
||||
});
|
||||
|
||||
return [...questionOptions, ...endingCardOptions];
|
||||
};
|
||||
|
||||
export const getOpeartorOptions = (variableType: TActionCalculateVariableType): ComboboxOption[] => {
|
||||
export const getActionVariableOptions = (localSurvey: TSurvey): ComboboxOption[] => {
|
||||
const variables = localSurvey.variables || [];
|
||||
|
||||
return variables.map((variable) => {
|
||||
return {
|
||||
icon: variable.type === "number" ? FileDigitIcon : FileType2Icon,
|
||||
label: variable.name,
|
||||
value: variable.id,
|
||||
meta: {
|
||||
variableType: variable.type,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const getActionOpeartorOptions = (variableType: TActionCalculateVariableType): ComboboxOption[] => {
|
||||
if (variableType === TActionCalculateVariableType.Number) {
|
||||
return [
|
||||
{
|
||||
@@ -108,26 +400,61 @@ export const getOpeartorOptions = (variableType: TActionCalculateVariableType):
|
||||
return [];
|
||||
};
|
||||
|
||||
export const getValueOptions = (
|
||||
questions: TSurveyQuestions,
|
||||
export const getActionValueOptions = (
|
||||
localSurvey: TSurvey,
|
||||
currQuestionIdx: number,
|
||||
hiddenFields: string[],
|
||||
userAttributes: string[]
|
||||
): ComboboxGroupedOption[] => {
|
||||
const hiddenFields = localSurvey.hiddenFields?.fieldIds || [];
|
||||
const variables = localSurvey.variables || [];
|
||||
const questions = localSurvey.questions;
|
||||
|
||||
const groupedOptions: ComboboxGroupedOption[] = [];
|
||||
|
||||
const questionOptions = getTargetOptions(questions, currQuestionIdx);
|
||||
// const questionOptions = getActionTargetOptions(questions, currQuestionIdx);
|
||||
const questionOptions = questions
|
||||
.filter((_, idx) => idx !== currQuestionIdx)
|
||||
.map((question) => {
|
||||
return {
|
||||
icon: questionIconMapping[question.type],
|
||||
label: getLocalizedValue(question.headline, "default"),
|
||||
value: question.id,
|
||||
meta: {
|
||||
fieldType: "question",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const variableOptions = variables.map((variable) => {
|
||||
return {
|
||||
icon: variable.type === "number" ? FileDigitIcon : FileType2Icon,
|
||||
label: variable.name,
|
||||
value: variable.id,
|
||||
meta: {
|
||||
fieldType: "variable",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const hiddenFieldsOptions = hiddenFields.map((field) => {
|
||||
return {
|
||||
icon: EyeOffIcon,
|
||||
label: field,
|
||||
value: field,
|
||||
meta: {
|
||||
fieldType: "hiddenField",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const userAttributesOptions = userAttributes.map((attribute) => {
|
||||
return {
|
||||
icon: TagIcon,
|
||||
label: attribute,
|
||||
value: attribute,
|
||||
meta: {
|
||||
fieldType: "userAttribute",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -139,6 +466,14 @@ export const getValueOptions = (
|
||||
});
|
||||
}
|
||||
|
||||
if (variableOptions.length > 0) {
|
||||
groupedOptions.push({
|
||||
label: "Variables",
|
||||
value: "variables",
|
||||
options: variableOptions,
|
||||
});
|
||||
}
|
||||
|
||||
if (hiddenFieldsOptions.length > 0) {
|
||||
groupedOptions.push({
|
||||
label: "Hidden Fields",
|
||||
|
||||
@@ -33,7 +33,7 @@ const Page = async ({ params }) => {
|
||||
organization,
|
||||
session,
|
||||
segments,
|
||||
{ hiddenFields, userAttributes },
|
||||
{ userAttributes },
|
||||
] = await Promise.all([
|
||||
getSurvey(params.surveyId),
|
||||
getProductByEnvironmentId(params.environmentId),
|
||||
@@ -89,7 +89,6 @@ const Page = async ({ params }) => {
|
||||
plan={organization.billing.plan}
|
||||
isFormbricksCloud={IS_FORMBRICKS_CLOUD}
|
||||
isUnsplashConfigured={UNSPLASH_ACCESS_KEY ? true : false}
|
||||
hiddenFields={hiddenFields}
|
||||
userAttributes={userAttributes}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,17 +1,31 @@
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import {
|
||||
TActionBase,
|
||||
TActionObjective,
|
||||
TConditionBase,
|
||||
TSurveyAdvancedLogic,
|
||||
} from "@formbricks/types/surveys/logic";
|
||||
import { TActionObjective, TConditionBase, TSurveyAdvancedLogic } from "@formbricks/types/surveys/logic";
|
||||
|
||||
export const performOperationsOnConditions = (action, advancedLogicCopy, logicIdx, resourceId, condition) => {
|
||||
export const performOperationsOnConditions = ({
|
||||
action,
|
||||
advancedLogicCopy,
|
||||
logicIdx,
|
||||
resourceId,
|
||||
condition,
|
||||
conditionBody,
|
||||
}: {
|
||||
action:
|
||||
| "addConditionBelow"
|
||||
| "toggleConnector"
|
||||
| "removeCondition"
|
||||
| "duplicateCondition"
|
||||
| "createGroup"
|
||||
| "updateCondition";
|
||||
advancedLogicCopy: TSurveyAdvancedLogic[];
|
||||
logicIdx: number;
|
||||
resourceId: string;
|
||||
condition?: TConditionBase;
|
||||
conditionBody?: Partial<TConditionBase>;
|
||||
}) => {
|
||||
const logicItem = advancedLogicCopy[logicIdx];
|
||||
|
||||
console.log("performOperationsOnConditions", action, resourceId, logicItem.conditions);
|
||||
|
||||
if (action === "addConditionBelow") {
|
||||
if (!condition) return;
|
||||
addConditionBelow(logicItem.conditions, resourceId, condition);
|
||||
} else if (action === "toggleConnector") {
|
||||
console.log("toggleConnector", resourceId, logicItem.conditions);
|
||||
@@ -22,6 +36,9 @@ export const performOperationsOnConditions = (action, advancedLogicCopy, logicId
|
||||
duplicateCondition(logicItem.conditions, resourceId);
|
||||
} else if (action === "createGroup") {
|
||||
createGroupFromResource(logicItem.conditions, resourceId);
|
||||
} else if (action === "updateCondition") {
|
||||
if (!conditionBody) return;
|
||||
updateCondition(logicItem.conditions, resourceId, conditionBody);
|
||||
}
|
||||
|
||||
advancedLogicCopy[logicIdx] = {
|
||||
@@ -30,13 +47,10 @@ export const performOperationsOnConditions = (action, advancedLogicCopy, logicId
|
||||
};
|
||||
};
|
||||
|
||||
export const performOperationsOnActions = () => {};
|
||||
|
||||
export const removeAction = (actions: TSurveyAdvancedLogic["actions"], idx: number) => {
|
||||
return actions.slice(0, idx).concat(actions.slice(idx + 1));
|
||||
};
|
||||
|
||||
// write the recursive function below, check if the conditions is of type group
|
||||
export const addConditionBelow = (
|
||||
group: TSurveyAdvancedLogic["conditions"],
|
||||
resourceId: string,
|
||||
@@ -126,6 +140,23 @@ export const createGroupFromResource = (group: TSurveyAdvancedLogic["conditions"
|
||||
}
|
||||
};
|
||||
|
||||
export const updateCondition = (
|
||||
group: TSurveyAdvancedLogic["conditions"],
|
||||
resourceId: string,
|
||||
condition: Partial<TConditionBase>
|
||||
) => {
|
||||
for (let i = 0; i < group.length; i++) {
|
||||
const { type, id } = group[i];
|
||||
|
||||
if (id === resourceId) {
|
||||
group[i] = { ...group[i], ...condition };
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === "group") updateCondition(group[i].conditions, resourceId, condition);
|
||||
}
|
||||
};
|
||||
|
||||
export const actionObjectiveOptions: { label: string; value: TActionObjective }[] = [
|
||||
{ label: "Calculate", value: TActionObjective.Calculate },
|
||||
{ label: "Require Answer", value: TActionObjective.RequireAnswer },
|
||||
|
||||
@@ -47,7 +47,11 @@ export const ZSurveyLogicCondition = z.enum([
|
||||
"isCompletelySubmitted",
|
||||
]);
|
||||
|
||||
const ZDyanmicLogicField = z.enum(["question", "variable", "attributeClass", "hiddenField"]);
|
||||
export type TSurveyLogicCondition = z.infer<typeof ZSurveyLogicCondition>;
|
||||
|
||||
export const ZDyanmicLogicField = z.enum(["question", "variable", "attributeClass", "hiddenField"]);
|
||||
|
||||
export type TDyanmicLogicField = z.infer<typeof ZDyanmicLogicField>;
|
||||
|
||||
const ZMatchValueBase = z.object({
|
||||
type: z.enum(["static", "dynamic"]),
|
||||
@@ -74,8 +78,8 @@ const ZConditionBase = z.object({
|
||||
connector: ZLogicalConnector.nullable(),
|
||||
type: ZDyanmicLogicField,
|
||||
conditionValue: z.string(),
|
||||
conditionOperator: ZSurveyLogicCondition,
|
||||
matchValue: ZMatchValue,
|
||||
conditionOperator: ZSurveyLogicCondition.nullable(),
|
||||
matchValue: ZMatchValue.nullable(),
|
||||
});
|
||||
|
||||
export type TConditionBase = z.infer<typeof ZConditionBase>;
|
||||
@@ -85,44 +89,41 @@ const ZConditionQuestionBase = ZConditionBase.extend({
|
||||
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([
|
||||
ZSurveyOpenTextQuestionInputType.enum.text,
|
||||
ZSurveyOpenTextQuestionInputType.enum.email,
|
||||
ZSurveyOpenTextQuestionInputType.enum.url,
|
||||
ZSurveyOpenTextQuestionInputType.enum.phone,
|
||||
]),
|
||||
inputType: z.enum(["text", "email", "url", "phone"]),
|
||||
conditionOperator: z.enum([
|
||||
ZSurveyLogicCondition.Enum.equals,
|
||||
ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
ZSurveyLogicCondition.Enum.contains,
|
||||
ZSurveyLogicCondition.Enum.doesNotContain,
|
||||
ZSurveyLogicCondition.Enum.startsWith,
|
||||
ZSurveyLogicCondition.Enum.doesNotStartWith,
|
||||
ZSurveyLogicCondition.Enum.endsWith,
|
||||
ZSurveyLogicCondition.Enum.doesNotEndWith,
|
||||
ZSurveyLogicCondition.Enum.isSubmitted,
|
||||
ZSurveyLogicCondition.Enum.isSkipped,
|
||||
"equals",
|
||||
"doesNotEqual",
|
||||
"contains",
|
||||
"doesNotContain",
|
||||
"startsWith",
|
||||
"doesNotStartWith",
|
||||
"endsWith",
|
||||
"doesNotEndWith",
|
||||
"isSubmitted",
|
||||
"isSkipped",
|
||||
]),
|
||||
matchValue: z.string().optional(),
|
||||
});
|
||||
|
||||
const ZOpenTextNumberConditon = ZOpenTextConditionBase.extend({
|
||||
inputType: z.literal(ZSurveyOpenTextQuestionInputType.enum.number),
|
||||
inputType: z.literal("number"),
|
||||
conditionOperator: z.enum([
|
||||
ZSurveyLogicCondition.Enum.equals,
|
||||
ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
ZSurveyLogicCondition.Enum.isGreaterThan,
|
||||
ZSurveyLogicCondition.Enum.isLessThan,
|
||||
ZSurveyLogicCondition.Enum.isGreaterThanOrEqual,
|
||||
ZSurveyLogicCondition.Enum.isLessThanOrEqual,
|
||||
ZSurveyLogicCondition.Enum.isSubmitted,
|
||||
ZSurveyLogicCondition.Enum.isSkipped,
|
||||
"equals",
|
||||
"doesNotEqual",
|
||||
"isGreaterThan",
|
||||
"isLessThan",
|
||||
"isGreaterThanOrEqual",
|
||||
"isLessThanOrEqual",
|
||||
"isSubmitted",
|
||||
"isSkipped",
|
||||
]),
|
||||
matchValue: z.number().optional(),
|
||||
});
|
||||
@@ -131,50 +132,44 @@ const ZOpenTextCondition = z.union([ZOpenTextStringConditon, ZOpenTextNumberCond
|
||||
|
||||
const ZMultipleChoiceSingleCondition = ZConditionQuestionBase.extend({
|
||||
questionType: z.literal(TSurveyQuestionTypeEnum.MultipleChoiceSingle),
|
||||
conditionOperator: z.enum([
|
||||
ZSurveyLogicCondition.Enum.equals,
|
||||
ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
ZSurveyLogicCondition.Enum.equalsOneOf,
|
||||
ZSurveyLogicCondition.Enum.isSubmitted,
|
||||
ZSurveyLogicCondition.Enum.isSkipped,
|
||||
]),
|
||||
conditionOperator: z.enum(["equals", "doesNotEqual", "equalsOneOf", "isSubmitted", "isSkipped"]),
|
||||
});
|
||||
|
||||
const ZMultipleChoiceMultiCondition = ZConditionQuestionBase.extend({
|
||||
questionType: z.literal(TSurveyQuestionTypeEnum.MultipleChoiceMulti),
|
||||
conditionOperator: z.enum([
|
||||
ZSurveyLogicCondition.Enum.equals,
|
||||
ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
ZSurveyLogicCondition.Enum.includesAllOf,
|
||||
ZSurveyLogicCondition.Enum.includesOneOf,
|
||||
ZSurveyLogicCondition.Enum.isSubmitted,
|
||||
ZSurveyLogicCondition.Enum.isSkipped,
|
||||
"equals",
|
||||
"doesNotEqual",
|
||||
"includesAllOf",
|
||||
"includesOneOf",
|
||||
"isSubmitted",
|
||||
"isSkipped",
|
||||
]),
|
||||
});
|
||||
|
||||
const ZPictureSelectionCondition = ZConditionQuestionBase.extend({
|
||||
questionType: z.literal(TSurveyQuestionTypeEnum.PictureSelection),
|
||||
conditionOperator: z.enum([
|
||||
ZSurveyLogicCondition.Enum.equals,
|
||||
ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
ZSurveyLogicCondition.Enum.includesAllOf,
|
||||
ZSurveyLogicCondition.Enum.includesOneOf,
|
||||
ZSurveyLogicCondition.Enum.isSubmitted,
|
||||
ZSurveyLogicCondition.Enum.isSkipped,
|
||||
"equals",
|
||||
"doesNotEqual",
|
||||
"includesAllOf",
|
||||
"includesOneOf",
|
||||
"isSubmitted",
|
||||
"isSkipped",
|
||||
]),
|
||||
});
|
||||
|
||||
const ZRatingCondition = ZConditionQuestionBase.extend({
|
||||
questionType: z.literal(TSurveyQuestionTypeEnum.Rating),
|
||||
conditionOperator: z.enum([
|
||||
ZSurveyLogicCondition.Enum.equals,
|
||||
ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
ZSurveyLogicCondition.Enum.isGreaterThan,
|
||||
ZSurveyLogicCondition.Enum.isLessThan,
|
||||
ZSurveyLogicCondition.Enum.isGreaterThanOrEqual,
|
||||
ZSurveyLogicCondition.Enum.isLessThanOrEqual,
|
||||
ZSurveyLogicCondition.Enum.isSubmitted,
|
||||
ZSurveyLogicCondition.Enum.isSkipped,
|
||||
"equals",
|
||||
"doesNotEqual",
|
||||
"isGreaterThan",
|
||||
"isLessThan",
|
||||
"isGreaterThanOrEqual",
|
||||
"isLessThanOrEqual",
|
||||
"isSubmitted",
|
||||
"isSkipped",
|
||||
]),
|
||||
matchValue: z.number().optional(),
|
||||
});
|
||||
@@ -182,68 +177,57 @@ const ZRatingCondition = ZConditionQuestionBase.extend({
|
||||
const ZNPSCondition = ZConditionQuestionBase.extend({
|
||||
questionType: z.literal(TSurveyQuestionTypeEnum.NPS),
|
||||
conditionOperator: z.enum([
|
||||
ZSurveyLogicCondition.Enum.equals,
|
||||
ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
ZSurveyLogicCondition.Enum.isGreaterThan,
|
||||
ZSurveyLogicCondition.Enum.isLessThan,
|
||||
ZSurveyLogicCondition.Enum.isGreaterThanOrEqual,
|
||||
ZSurveyLogicCondition.Enum.isLessThanOrEqual,
|
||||
ZSurveyLogicCondition.Enum.isSubmitted,
|
||||
ZSurveyLogicCondition.Enum.isSkipped,
|
||||
"equals",
|
||||
"doesNotEqual",
|
||||
"isGreaterThan",
|
||||
"isLessThan",
|
||||
"isGreaterThanOrEqual",
|
||||
"isLessThanOrEqual",
|
||||
"isSubmitted",
|
||||
"isSkipped",
|
||||
]),
|
||||
matchValue: z.number().optional(),
|
||||
});
|
||||
|
||||
const ZCTACondition = ZConditionQuestionBase.extend({
|
||||
questionType: z.literal(TSurveyQuestionTypeEnum.CTA),
|
||||
conditionOperator: z.enum([ZSurveyLogicCondition.Enum.isClicked, ZSurveyLogicCondition.Enum.isSkipped]),
|
||||
conditionOperator: z.enum(["isClicked", "isSkipped"]),
|
||||
matchValue: z.undefined(),
|
||||
});
|
||||
|
||||
const ZConsentCondition = ZConditionQuestionBase.extend({
|
||||
questionType: z.literal(TSurveyQuestionTypeEnum.Consent),
|
||||
conditionOperator: z.enum([ZSurveyLogicCondition.Enum.isAccepted, ZSurveyLogicCondition.Enum.isSkipped]),
|
||||
conditionOperator: z.enum(["isAccepted", "isSkipped"]),
|
||||
matchValue: z.undefined(),
|
||||
});
|
||||
|
||||
const ZDateCondition = ZConditionQuestionBase.extend({
|
||||
questionType: z.literal(TSurveyQuestionTypeEnum.Date),
|
||||
conditionOperator: z.enum([
|
||||
ZSurveyLogicCondition.Enum.equals,
|
||||
ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
ZSurveyLogicCondition.Enum.isBefore,
|
||||
ZSurveyLogicCondition.Enum.isAfter,
|
||||
ZSurveyLogicCondition.Enum.isSubmitted,
|
||||
ZSurveyLogicCondition.Enum.isSkipped,
|
||||
]),
|
||||
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([ZSurveyLogicCondition.Enum.isSubmitted, ZSurveyLogicCondition.Enum.isSkipped]),
|
||||
conditionOperator: z.enum(["isSubmitted", "isSkipped"]),
|
||||
matchValue: z.undefined(),
|
||||
});
|
||||
|
||||
const ZCalCondition = ZConditionQuestionBase.extend({
|
||||
questionType: z.literal(TSurveyQuestionTypeEnum.Cal),
|
||||
conditionOperator: z.enum([ZSurveyLogicCondition.Enum.isBooked, ZSurveyLogicCondition.Enum.isSkipped]),
|
||||
conditionOperator: z.enum(["isBooked", "isSkipped"]),
|
||||
matchValue: z.undefined(),
|
||||
});
|
||||
|
||||
const ZMatrixCondition = ZConditionQuestionBase.extend({
|
||||
questionType: z.literal(TSurveyQuestionTypeEnum.Matrix),
|
||||
conditionOperator: z.enum([
|
||||
ZSurveyLogicCondition.Enum.isPartiallySubmitted,
|
||||
ZSurveyLogicCondition.Enum.isCompletelySubmitted,
|
||||
ZSurveyLogicCondition.Enum.isSkipped,
|
||||
]),
|
||||
conditionOperator: z.enum(["isPartiallySubmitted", "isCompletelySubmitted", "isSkipped"]),
|
||||
matchValue: z.undefined(),
|
||||
});
|
||||
|
||||
const ZAddressCondition = ZConditionQuestionBase.extend({
|
||||
questionType: z.literal(TSurveyQuestionTypeEnum.Address),
|
||||
conditionOperator: z.enum([ZSurveyLogicCondition.Enum.isSubmitted, ZSurveyLogicCondition.Enum.isSkipped]),
|
||||
conditionOperator: z.enum(["isSubmitted", "isSkipped"]),
|
||||
matchValue: z.undefined(),
|
||||
});
|
||||
|
||||
@@ -271,26 +255,26 @@ const ZConditionVariableBase = ZConditionBase.extend({
|
||||
const ZConditionTextVariable = ZConditionVariableBase.extend({
|
||||
variableType: z.literal("text"),
|
||||
conditionOperator: z.enum([
|
||||
ZSurveyLogicCondition.Enum.equals,
|
||||
ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
ZSurveyLogicCondition.Enum.contains,
|
||||
ZSurveyLogicCondition.Enum.doesNotContain,
|
||||
ZSurveyLogicCondition.Enum.startsWith,
|
||||
ZSurveyLogicCondition.Enum.doesNotStartWith,
|
||||
ZSurveyLogicCondition.Enum.endsWith,
|
||||
ZSurveyLogicCondition.Enum.doesNotEndWith,
|
||||
"equals",
|
||||
"doesNotEqual",
|
||||
"contains",
|
||||
"doesNotContain",
|
||||
"startsWith",
|
||||
"doesNotStartWith",
|
||||
"endsWith",
|
||||
"doesNotEndWith",
|
||||
]),
|
||||
});
|
||||
|
||||
const ZConditionNumberVariable = ZConditionVariableBase.extend({
|
||||
variableType: z.literal("number"),
|
||||
conditionOperator: z.enum([
|
||||
ZSurveyLogicCondition.Enum.equals,
|
||||
ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
ZSurveyLogicCondition.Enum.isGreaterThan,
|
||||
ZSurveyLogicCondition.Enum.isLessThan,
|
||||
ZSurveyLogicCondition.Enum.isGreaterThanOrEqual,
|
||||
ZSurveyLogicCondition.Enum.isLessThanOrEqual,
|
||||
"equals",
|
||||
"doesNotEqual",
|
||||
"isGreaterThan",
|
||||
"isLessThan",
|
||||
"isGreaterThanOrEqual",
|
||||
"isLessThanOrEqual",
|
||||
]),
|
||||
});
|
||||
|
||||
@@ -299,28 +283,28 @@ const ZConditionVariable = z.union([ZConditionTextVariable, ZConditionNumberVari
|
||||
const ZConditionAttributeClass = ZConditionBase.extend({
|
||||
type: z.literal("attributeClass"),
|
||||
conditionOperator: z.enum([
|
||||
ZSurveyLogicCondition.Enum.equals,
|
||||
ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
ZSurveyLogicCondition.Enum.contains,
|
||||
ZSurveyLogicCondition.Enum.doesNotContain,
|
||||
ZSurveyLogicCondition.Enum.startsWith,
|
||||
ZSurveyLogicCondition.Enum.doesNotStartWith,
|
||||
ZSurveyLogicCondition.Enum.endsWith,
|
||||
ZSurveyLogicCondition.Enum.doesNotEndWith,
|
||||
"equals",
|
||||
"doesNotEqual",
|
||||
"contains",
|
||||
"doesNotContain",
|
||||
"startsWith",
|
||||
"doesNotStartWith",
|
||||
"endsWith",
|
||||
"doesNotEndWith",
|
||||
]),
|
||||
});
|
||||
|
||||
const ZConditionHiddenField = ZConditionBase.extend({
|
||||
type: z.literal("hiddenField"),
|
||||
conditionOperator: z.enum([
|
||||
ZSurveyLogicCondition.Enum.equals,
|
||||
ZSurveyLogicCondition.Enum.doesNotEqual,
|
||||
ZSurveyLogicCondition.Enum.contains,
|
||||
ZSurveyLogicCondition.Enum.doesNotContain,
|
||||
ZSurveyLogicCondition.Enum.startsWith,
|
||||
ZSurveyLogicCondition.Enum.doesNotStartWith,
|
||||
ZSurveyLogicCondition.Enum.endsWith,
|
||||
ZSurveyLogicCondition.Enum.doesNotEndWith,
|
||||
"equals",
|
||||
"doesNotEqual",
|
||||
"contains",
|
||||
"doesNotContain",
|
||||
"startsWith",
|
||||
"doesNotStartWith",
|
||||
"endsWith",
|
||||
"doesNotEndWith",
|
||||
]),
|
||||
});
|
||||
|
||||
@@ -344,6 +328,7 @@ export const ZActionObjective = z.nativeEnum(TActionObjective);
|
||||
const ZActionBase = z.object({
|
||||
id: z.string().cuid2(),
|
||||
objective: ZActionObjective,
|
||||
target: z.string(),
|
||||
});
|
||||
|
||||
export type TActionBase = z.infer<typeof ZActionBase>;
|
||||
@@ -371,7 +356,7 @@ export const ZActionTextVariableCalculateOperator = z.nativeEnum(TActionTextVari
|
||||
const ZActionTextVariableCalculate = ZActionCalculateBase.extend({
|
||||
variableType: z.literal(TActionCalculateVariableType.Text),
|
||||
operator: ZActionTextVariableCalculateOperator,
|
||||
value: z.string(),
|
||||
value: z.union([z.string(), ZMatchValueDynamic]),
|
||||
});
|
||||
|
||||
export enum TActionNumberVariableCalculateOperator {
|
||||
@@ -387,10 +372,12 @@ export const ZActionNumberVariableCalculateOperator = z.nativeEnum(TActionNumber
|
||||
const ZActionNumberVariableCalculate = ZActionCalculateBase.extend({
|
||||
variableType: z.literal(TActionCalculateVariableType.Number),
|
||||
operator: ZActionNumberVariableCalculateOperator,
|
||||
value: z.number(),
|
||||
value: z.union([z.number(), ZMatchValueDynamic]),
|
||||
});
|
||||
|
||||
const ZActionCalculate = z.union([ZActionTextVariableCalculate, ZActionNumberVariableCalculate]);
|
||||
export const ZActionCalculate = z.union([ZActionTextVariableCalculate, ZActionNumberVariableCalculate]);
|
||||
|
||||
export type TActionCalculate = z.infer<typeof ZActionCalculate>;
|
||||
|
||||
const ZActionRequireAnswer = ZActionBase.extend({
|
||||
objective: z.literal("requireAnswer"),
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface ComboboxOption {
|
||||
icon?: ForwardRefExoticComponent<Omit<LucideProps, "ref"> & RefAttributes<SVGSVGElement>>;
|
||||
label: string;
|
||||
value: string;
|
||||
meta?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface ComboboxGroupedOption {
|
||||
@@ -32,7 +33,7 @@ interface InputComboboxProps {
|
||||
options?: ComboboxOption[];
|
||||
groupedOptions?: ComboboxGroupedOption[];
|
||||
selected?: string | string[] | null;
|
||||
onChangeValue: (option: string | string[]) => void;
|
||||
onChangeValue: (value: string | string[], option?: ComboboxOption) => void;
|
||||
inputProps?: React.ComponentProps<typeof Input>;
|
||||
withInput?: boolean;
|
||||
comboboxSize?: "sm" | "lg";
|
||||
@@ -56,12 +57,16 @@ export const InputCombobox = ({
|
||||
comboboxClasses,
|
||||
}: InputComboboxProps) => {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [value, setValue] = React.useState<ComboboxOption | ComboboxOption[] | null>(() => {
|
||||
const [value, setValue] = React.useState<ComboboxOption | ComboboxOption[] | null>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
const validOptions = options?.length ? options : groupedOptions?.flatMap((group) => group.options);
|
||||
if (Array.isArray(selected)) {
|
||||
return options?.filter((option) => selected.includes(option.value)) || null;
|
||||
setValue(validOptions?.filter((option) => selected.includes(option.value)) || null);
|
||||
} else {
|
||||
setValue(validOptions?.find((option) => option.value === selected) || null);
|
||||
}
|
||||
return options?.find((option) => option.value === selected) || null;
|
||||
});
|
||||
}, [selected, options, groupedOptions]);
|
||||
|
||||
const handleSelect = (option: ComboboxOption) => {
|
||||
if (allowMultiSelect) {
|
||||
@@ -71,11 +76,11 @@ export const InputCombobox = ({
|
||||
onChangeValue(newValue.map((item) => item.value));
|
||||
setValue(newValue);
|
||||
} else {
|
||||
onChangeValue([option.value]);
|
||||
onChangeValue([option.value], option);
|
||||
setValue([option]);
|
||||
}
|
||||
} else {
|
||||
onChangeValue(option.value);
|
||||
onChangeValue(option.value, option);
|
||||
setValue(option);
|
||||
setOpen(false);
|
||||
}
|
||||
@@ -107,21 +112,21 @@ export const InputCombobox = ({
|
||||
<>
|
||||
{idx !== 0 && <span>,</span>}
|
||||
<div className="flex items-center gap-2">
|
||||
{item?.icon && <item.icon className="h-4 w-4 text-slate-300" />}
|
||||
{item?.icon && <item.icon className="h-5 w-5 shrink-0 text-slate-400" />}
|
||||
<span>{item?.label}</span>
|
||||
</div>
|
||||
</>
|
||||
))
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
{value?.icon && <value.icon className="h-4 w-4 text-slate-300" />}
|
||||
{value?.icon && <value.icon className="h-5 w-5 shrink-0 text-slate-400" />}
|
||||
<span>{value?.label}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<ChevronDownIcon
|
||||
className="text-slate-300"
|
||||
className="shrink-0 text-slate-300"
|
||||
height={comboboxSize === "sm" ? 20 : 16}
|
||||
width={comboboxSize === "sm" ? 20 : 16}
|
||||
/>
|
||||
@@ -150,7 +155,7 @@ export const InputCombobox = ({
|
||||
(!allowMultiSelect && typeof value === "string" && value === option.value)) && (
|
||||
<CheckIcon className="mr-2 h-4 w-4 text-slate-300" />
|
||||
)}
|
||||
{option.icon && <option.icon className="mr-2 h-4 w-4 shrink-0 text-slate-400" />}
|
||||
{option.icon && <option.icon className="mr-2 h-5 w-5 shrink-0 text-slate-400" />}
|
||||
{option.label}
|
||||
</CommandItem>
|
||||
))}
|
||||
@@ -169,7 +174,7 @@ export const InputCombobox = ({
|
||||
(!allowMultiSelect && typeof value === "string" && value === option.value)) && (
|
||||
<CheckIcon className="mr-2 h-4 w-4 shrink-0 text-slate-300" />
|
||||
)}
|
||||
{option.icon && <option.icon className="mr-2 h-4 w-4 text-slate-300" />}
|
||||
{option.icon && <option.icon className="mr-2 h-5 w-5 shrink-0 text-slate-400" />}
|
||||
{option.label}
|
||||
</CommandItem>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user