From 7dd8bb95bdb9bcad65ece3ef0b373f46d470a11f Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Tue, 3 Sep 2024 10:38:13 +0530 Subject: [PATCH] fix: delete logicItem in case of no conditions --- .../edit/components/AdvancedLogicEditor.tsx | 50 +++-- .../components/AdvancedLogicEditorActions.tsx | 35 ++-- .../AdvancedLogicEditorConditions.tsx | 22 +-- .../edit/components/ConditionalLogic.tsx | 5 +- packages/lib/survey/logic/utils.ts | 8 +- packages/ui/InputCombobox/index.tsx | 178 +++++++++++------- packages/ui/InputCombobox/stories.ts | 4 +- 7 files changed, 172 insertions(+), 130 deletions(-) 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 56525fc598..7bf9b4191b 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 @@ -25,31 +25,29 @@ export function AdvancedLogicEditor({ isLast, }: AdvancedLogicEditorProps) { return ( - <> -
- - - {isLast && ( -
- -

All other answers will continue to the next question

-
- )} -
- +
+ + + {isLast && ( +
+ +

All other answers will continue to the next question

+
+ )} +
); } 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 87b2c7e07f..61560e7ac2 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 @@ -92,7 +92,7 @@ export function AdvancedLogicEditorActions({ key="objective" showSearch={false} options={actionObjectiveOptions} - selected={action.objective} + value={action.objective} onChangeValue={(val: TActionObjective) => { updateAction(idx, { objective: val, @@ -109,7 +109,7 @@ export function AdvancedLogicEditorActions({ ? getActionVariableOptions(localSurvey) : getActionTargetOptions(action, localSurvey, questionIdx) } - selected={action.objective === "calculate" ? action.variableId : action.target} + value={action.objective === "calculate" ? action.variableId : action.target} onChangeValue={(val: string) => { updateAction(idx, { ...(action.objective === "calculate" ? { variableId: val } : { target: val }), @@ -126,7 +126,7 @@ export function AdvancedLogicEditorActions({ options={getActionOpeartorOptions( localSurvey.variables.find((v) => v.id === action.variableId)?.type )} - selected={action.operator} + value={action.operator} onChangeValue={(val: TActionVariableCalculateOperator) => { updateAction(idx, { operator: val, @@ -139,24 +139,25 @@ export function AdvancedLogicEditorActions({ key="value" withInput={true} clearable={true} + value={action.value?.value ?? ""} inputProps={{ placeholder: "Value", - value: action.value?.value ?? "", type: localSurvey.variables.find((v) => v.id === action.variableId)?.type || "text", - onChange: (e) => { - let val: string | number = e.target.value; + // value: action.value?.value ?? "", + // onChange: (e) => { + // let val: string | number = e.target.value; - const variable = localSurvey.variables.find((v) => v.id === action.variableId); - if (variable?.type === "number") { - val = Number(val); - } - updateAction(idx, { - value: { - type: "static", - value: val, - }, - }); - }, + // const variable = localSurvey.variables.find((v) => v.id === action.variableId); + // if (variable?.type === "number") { + // val = Number(val); + // } + // updateAction(idx, { + // value: { + // type: "static", + // value: val, + // }, + // }); + // }, }} groupedOptions={getActionValueOptions(action.variableId, localSurvey, questionIdx)} onChangeValue={(val: string, option) => { 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 ee7416b8a7..41337dcda6 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 @@ -75,6 +75,11 @@ export function AdvancedLogicEditorConditions({ const logicItem = logicCopy[logicIdx]; removeCondition(logicItem.conditions, resourceId); + // Remove the logic item if there are no conditions left + if (logicItem.conditions.conditions.length === 0) { + logicCopy.splice(logicIdx, 1); + } + updateQuestion(questionIdx, { logic: logicCopy, }); @@ -110,8 +115,6 @@ export function AdvancedLogicEditorConditions({ }); }; - console.log("conditions", conditions); - const renderCondition = ( condition: TSingleCondition | TConditionGroup, index: number, @@ -203,7 +206,7 @@ export function AdvancedLogicEditorConditions({ key="conditionValue" showSearch={false} groupedOptions={conditionValueOptions} - selected={condition.leftOperand.id} + value={condition.leftOperand.id} onChangeValue={(val: string, option) => { handleUpdateCondition(condition.id, { leftOperand: { @@ -218,7 +221,7 @@ export function AdvancedLogicEditorConditions({ key="conditionOperator" showSearch={false} options={conditionOperatorOptions} - selected={condition.operator} + value={condition.operator} onChangeValue={(val: TSurveyLogicCondition) => { handleUpdateCondition(condition.id, { operator: val, @@ -232,22 +235,13 @@ export function AdvancedLogicEditorConditions({ inputProps={{ type: inputType, placeholder: "Value", - value: condition.rightOperand?.value, - onChange: (e) => { - handleUpdateCondition(condition.id, { - rightOperand: { - type: "static", - value: e.target.value, - }, - }); - }, }} key="conditionMatchValue" showSearch={false} groupedOptions={options} allowMultiSelect={["equalsOneOf", "includesAllOf", "includesOneOf"].includes(condition.operator)} comboboxClasses="grow min-w-[100px] max-w-[300px]" - selected={condition.rightOperand?.value} + value={condition.rightOperand?.value} clearable={true} onChangeValue={(val: TRightOperand["value"], option) => { handleUpdateCondition(condition.id, { 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 486b758f81..3a2200cac4 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 @@ -85,9 +85,8 @@ export function ConditionalLogic({ const duplicateLogic = (logicItemIdx: number) => { const logicCopy = structuredClone(question.logic || []); - const lc = logicCopy[logicItemIdx]; - console.log(lc); - const newLogicItem = duplicateLogicItem(lc); + const logicItem = logicCopy[logicItemIdx]; + const newLogicItem = duplicateLogicItem(logicItem); logicCopy.splice(logicItemIdx + 1, 0, newLogicItem); updateQuestion(questionIdx, { diff --git a/packages/lib/survey/logic/utils.ts b/packages/lib/survey/logic/utils.ts index 04aafc964a..646f98008e 100644 --- a/packages/lib/survey/logic/utils.ts +++ b/packages/lib/survey/logic/utils.ts @@ -57,7 +57,7 @@ export const addConditionBelow = ( for (let i = 0; i < group.conditions.length; i++) { const item = group.conditions[i]; - if (item.connector) { + if (isConditionsGroup(item)) { if (item.id === resourceId) { group.conditions.splice(i + 1, 0, condition); break; @@ -95,7 +95,7 @@ export const removeCondition = (group: TConditionGroup, resourceId: string) => { return; } - if (item.connector) { + if (isConditionsGroup(item)) { removeCondition(item, resourceId); } } @@ -149,7 +149,7 @@ export const createGroupFromResource = (group: TConditionGroup, resourceId: stri return; } - if (item.connector) { + if (isConditionsGroup(item)) { createGroupFromResource(item, resourceId); } } @@ -168,7 +168,7 @@ export const updateCondition = ( return; } - if (item.connector) { + if (isConditionsGroup(item)) { updateCondition(item, resourceId, condition); } } diff --git a/packages/ui/InputCombobox/index.tsx b/packages/ui/InputCombobox/index.tsx index e1b6a851f3..51ffcecfb6 100644 --- a/packages/ui/InputCombobox/index.tsx +++ b/packages/ui/InputCombobox/index.tsx @@ -1,5 +1,5 @@ import { CheckIcon, ChevronDownIcon, LucideProps, XIcon } from "lucide-react"; -import React, { useEffect } from "react"; +import React, { useEffect, useMemo } from "react"; import { ForwardRefExoticComponent, RefAttributes } from "react"; import { cn } from "@formbricks/lib/cn"; import { @@ -32,9 +32,9 @@ interface InputComboboxProps { searchPlaceholder?: string; options?: ComboboxOption[]; groupedOptions?: ComboboxGroupedOption[]; - selected?: string | number | string[] | null; + value?: string | number | string[] | null; onChangeValue: (value: T | T[], option?: ComboboxOption) => void; - inputProps?: React.ComponentProps; + inputProps?: Omit, "value" | "onChange">; clearable?: boolean; withInput?: boolean; comboboxSize?: "sm" | "lg"; @@ -49,62 +49,134 @@ export const InputCombobox = ({ options, inputProps, groupedOptions, - selected, + value, onChangeValue, clearable = false, withInput = false, - // comboboxSize = "lg", allowMultiSelect = false, showCheckIcon = false, comboboxClasses, }: InputComboboxProps) => { const [open, setOpen] = React.useState(false); - const [value, setValue] = React.useState(null); - const [hideInput, setHideInput] = React.useState(false); + const [localValue, setLocalValue] = React.useState< + ComboboxOption | ComboboxOption[] | string | number | null + >(null); + const [inputType, setInputType] = React.useState<"dropdown" | "input" | null>(null); + + showCheckIcon = allowMultiSelect ? true : showCheckIcon; useEffect(() => { const validOptions = options?.length ? options : groupedOptions?.flatMap((group) => group.options); - if (validOptions?.find((option) => option.value === selected)) { - setHideInput(true); - } - }, []); - React.useEffect(() => { - const validOptions = options?.length ? options : groupedOptions?.flatMap((group) => group.options); - if (Array.isArray(selected)) { - setValue(validOptions?.filter((option) => selected.includes(option.value as string)) || null); + if (value === null || value === undefined) { + setLocalValue(null); } else { - setValue(validOptions?.find((option) => option.value === selected) || null); + if (Array.isArray(value)) { + if (value.length > 0) { + setLocalValue(validOptions?.filter((option) => value.includes(option.value as string)) || null); + if (inputType !== "dropdown") { + setInputType("dropdown"); + } + } + } else { + const option = validOptions?.find((option) => option.value === value); + if (option) { + setLocalValue(option); + if (inputType !== "dropdown") { + setInputType("dropdown"); + } + } else { + if (withInput) { + setLocalValue(value); + if (inputType !== "input") { + setInputType("input"); + } + } + } + } } - }, [selected, options, groupedOptions]); + }, [value, options, groupedOptions, inputType, withInput]); const handleSelect = (option: ComboboxOption) => { - let shouldHideInput = true; + if (inputType !== "dropdown") { + setInputType("dropdown"); + } + if (allowMultiSelect) { - if (Array.isArray(value)) { - const doesExist = value.find((item) => item.value === option.value); - const newValue = doesExist ? value.filter((item) => item.value !== option.value) : [...value, option]; + if (Array.isArray(localValue)) { + const doesExist = localValue.find((item) => item.value === option.value); + const newValue = doesExist + ? localValue.filter((item) => item.value !== option.value) + : [...localValue, option]; + if (!newValue.length) { - shouldHideInput = false; + onChangeValue(""); + setInputType(null); } onChangeValue(newValue.map((item) => item.value)); - setValue(newValue); + setLocalValue(newValue); } else { onChangeValue([option.value], option); - setValue([option]); + setLocalValue([option]); } } else { onChangeValue(option.value, option); - setValue(option); + setLocalValue(option); setOpen(false); } - if (withInput) setHideInput(shouldHideInput); + }; + + const onInputChange = (e: React.ChangeEvent) => { + const value = e.target.value; + if (value === "") { + setLocalValue(null); + onChangeValue(""); + } + + if (inputType !== "input") { + setInputType("input"); + } + + setLocalValue(value); + onChangeValue(value); + }; + + const getDisplayValue = useMemo(() => { + if (Array.isArray(localValue)) { + return localValue.map((item, idx) => ( + <> + {idx !== 0 && ,} +
+ {item.icon && } + {item.label} +
+ + )); + } else if (localValue && typeof localValue === "object") { + return ( +
+ {localValue.icon && } + {localValue.label} +
+ ); + } + }, [localValue]); + + const handleClear = () => { + setInputType(null); + onChangeValue(""); + setLocalValue(null); }; return (
- {withInput && !hideInput && ( - + {withInput && inputType !== "dropdown" && ( + )} @@ -112,36 +184,10 @@ export const InputCombobox = ({ role="combobox" aria-controls="options" aria-expanded={open} - className={cn("flex h-10 w-full cursor-pointer items-center justify-center rounded-md bg-white", { - "rounded-l-none": withInput, - })}> -
- {Array.isArray(value) ? ( - value.map((item, idx) => ( - <> - {idx !== 0 && ,} -
- {item?.icon && } - {item?.label} -
- - )) - ) : ( -
- {value?.icon && } - {value?.label} -
- )} -
- {clearable && selected ? ( - { - onChangeValue(""); - setValue(null); - setHideInput(false); - }} - /> + className={cn("flex h-10 w-full cursor-pointer items-center justify-center rounded-md bg-white")}> +
{getDisplayValue}
+ {clearable && inputType === "dropdown" ? ( + ) : ( )} @@ -172,9 +218,11 @@ export const InputCombobox = ({ className="cursor-pointer truncate"> {showCheckIcon && ((allowMultiSelect && - Array.isArray(value) && - value.find((item) => item.value === option.value)) || - (!allowMultiSelect && typeof value === "string" && value === option.value)) && ( + Array.isArray(localValue) && + localValue.find((item) => item.value === option.value)) || + (!allowMultiSelect && + typeof localValue === "string" && + localValue === option.value)) && ( )} {option.icon && } @@ -195,9 +243,11 @@ export const InputCombobox = ({ className="cursor-pointer truncate"> {showCheckIcon && ((allowMultiSelect && - Array.isArray(value) && - value.find((item) => item.value === option.value)) || - (!allowMultiSelect && typeof value === "string" && value === option.value)) && ( + Array.isArray(localValue) && + localValue.find((item) => item.value === option.value)) || + (!allowMultiSelect && + typeof localValue === "string" && + localValue === option.value)) && ( )} {option.icon && } diff --git a/packages/ui/InputCombobox/stories.ts b/packages/ui/InputCombobox/stories.ts index 851f32c43c..e7273be28e 100644 --- a/packages/ui/InputCombobox/stories.ts +++ b/packages/ui/InputCombobox/stories.ts @@ -35,7 +35,7 @@ export const Default: Story = { { label: "Option 2", value: "option2" }, { label: "Option 3", value: "option3" }, ], - selected: null, + value: null, onChangeValue: (option) => console.log(option), withInput: false, allowMultiSelect: false, @@ -80,7 +80,7 @@ export const MultiSelect: Story = { args: { ...Default.args, allowMultiSelect: true, - selected: ["option1", "option3"], + value: ["option1", "option3"], }, };