diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedLogicEditor.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedLogicEditor.tsx index 2511fd740b..3e14529252 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedLogicEditor.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedLogicEditor.tsx @@ -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 + ) => { 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} /> ); diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedLogicEditorActions.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedLogicEditorActions.tsx index 18b77a3c0b..5cb8533c54 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedLogicEditorActions.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedLogicEditorActions.tsx @@ -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 + ) => 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) => { + handleActionsChange("update", actionIdx, updatedAction); + }; + + console.log("actions", actions); return (
@@ -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]" /> {}} - 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" && ( <> {}} + onChangeValue={( + val: TActionNumberVariableCalculateOperator | TActionTextVariableCalculateOperator + ) => { + updateAction(idx, { + operator: val, + }); + }} + comboboxClasses="min-w-[100px]" /> {}} - 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" /> diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedLogicEditorConditions.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedLogicEditorConditions.tsx index 97f9c2e460..fb0d1222cc 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedLogicEditorConditions.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedLogicEditorConditions.tsx @@ -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) => { + 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) => { + 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} />
@@ -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 (
@@ -154,10 +198,43 @@ export function AdvancedLogicEditorConditions({
- + { + handleUpdateCondition(id, { + conditionValue: val, + ...option?.meta, + }); + }} + comboboxClasses="grow" + /> + { + console.log("val", val, option); + handleUpdateCondition(id, { + conditionOperator: val, + }); + }} + comboboxClasses="grow" + /> + {show && options.length > 0 && ( + {}} + /> + )} @@ -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, }); }}> diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedSettings.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedSettings.tsx index 9ce7ff0c0a..6d95be79b4 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedSettings.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedSettings.tsx @@ -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} />
diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ConditionalLogic.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ConditionalLogic.tsx index 4eccbc6ac0..8ac4a340c3 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ConditionalLogic.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ConditionalLogic.tsx @@ -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) => (
)} @@ -150,7 +155,7 @@ export const InputCombobox = ({ (!allowMultiSelect && typeof value === "string" && value === option.value)) && ( )} - {option.icon && } + {option.icon && } {option.label} ))} @@ -169,7 +174,7 @@ export const InputCombobox = ({ (!allowMultiSelect && typeof value === "string" && value === option.value)) && ( )} - {option.icon && } + {option.icon && } {option.label} ))}