mirror of
https://github.com/formbricks/formbricks.git
synced 2026-02-04 10:30:00 -06:00
fix: delete logicItem in case of no conditions
This commit is contained in:
@@ -25,31 +25,29 @@ export function AdvancedLogicEditor({
|
||||
isLast,
|
||||
}: AdvancedLogicEditorProps) {
|
||||
return (
|
||||
<>
|
||||
<div className={cn("flex w-full flex-col gap-4 text-sm")}>
|
||||
<AdvancedLogicEditorConditions
|
||||
conditions={logicItem.conditions}
|
||||
updateQuestion={updateQuestion}
|
||||
question={question}
|
||||
questionIdx={questionIdx}
|
||||
localSurvey={localSurvey}
|
||||
logicIdx={logicIdx}
|
||||
/>
|
||||
<AdvancedLogicEditorActions
|
||||
logicItem={logicItem}
|
||||
logicIdx={logicIdx}
|
||||
question={question}
|
||||
updateQuestion={updateQuestion}
|
||||
localSurvey={localSurvey}
|
||||
questionIdx={questionIdx}
|
||||
/>
|
||||
{isLast && (
|
||||
<div className="flex flex-wrap items-center space-x-2">
|
||||
<ArrowRightIcon className="h-4 w-4" />
|
||||
<p className="text-slate-700">All other answers will continue to the next question</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
<div className={cn("flex w-full flex-col gap-4 text-sm")}>
|
||||
<AdvancedLogicEditorConditions
|
||||
conditions={logicItem.conditions}
|
||||
updateQuestion={updateQuestion}
|
||||
question={question}
|
||||
questionIdx={questionIdx}
|
||||
localSurvey={localSurvey}
|
||||
logicIdx={logicIdx}
|
||||
/>
|
||||
<AdvancedLogicEditorActions
|
||||
logicItem={logicItem}
|
||||
logicIdx={logicIdx}
|
||||
question={question}
|
||||
updateQuestion={updateQuestion}
|
||||
localSurvey={localSurvey}
|
||||
questionIdx={questionIdx}
|
||||
/>
|
||||
{isLast && (
|
||||
<div className="flex flex-wrap items-center space-x-2">
|
||||
<ArrowRightIcon className="h-4 w-4" />
|
||||
<p className="text-slate-700">All other answers will continue to the next question</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T> {
|
||||
searchPlaceholder?: string;
|
||||
options?: ComboboxOption<T>[];
|
||||
groupedOptions?: ComboboxGroupedOption[];
|
||||
selected?: string | number | string[] | null;
|
||||
value?: string | number | string[] | null;
|
||||
onChangeValue: (value: T | T[], option?: ComboboxOption) => void;
|
||||
inputProps?: React.ComponentProps<typeof Input>;
|
||||
inputProps?: Omit<React.ComponentProps<typeof Input>, "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<string | number>) => {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [value, setValue] = React.useState<ComboboxOption | ComboboxOption[] | null>(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<HTMLInputElement>) => {
|
||||
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 && <span>,</span>}
|
||||
<div className="flex items-center gap-2">
|
||||
{item.icon && <item.icon className="h-5 w-5 shrink-0 text-slate-400" />}
|
||||
<span>{item.label}</span>
|
||||
</div>
|
||||
</>
|
||||
));
|
||||
} else if (localValue && typeof localValue === "object") {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
{localValue.icon && <localValue.icon className="h-5 w-5 shrink-0 text-slate-400" />}
|
||||
<span>{localValue.label}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}, [localValue]);
|
||||
|
||||
const handleClear = () => {
|
||||
setInputType(null);
|
||||
onChangeValue("");
|
||||
setLocalValue(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn("flex overflow-hidden rounded-md border border-slate-300", comboboxClasses)}>
|
||||
{withInput && !hideInput && (
|
||||
<Input className="min-w-0 rounded-none border-0 border-r border-slate-300 bg-white" {...inputProps} />
|
||||
{withInput && inputType !== "dropdown" && (
|
||||
<Input
|
||||
className="min-w-0 rounded-none border-0 border-r border-slate-300 bg-white focus:border-slate-300"
|
||||
{...inputProps}
|
||||
value={value as string | number}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
)}
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
@@ -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,
|
||||
})}>
|
||||
<div className="ellipsis flex w-full gap-2 truncate px-2">
|
||||
{Array.isArray(value) ? (
|
||||
value.map((item, idx) => (
|
||||
<>
|
||||
{idx !== 0 && <span>,</span>}
|
||||
<div className="flex items-center gap-2">
|
||||
{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-5 w-5 shrink-0 text-slate-400" />}
|
||||
<span>{value?.label}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{clearable && selected ? (
|
||||
<XIcon
|
||||
className="shrink-0 text-slate-300"
|
||||
onClick={() => {
|
||||
onChangeValue("");
|
||||
setValue(null);
|
||||
setHideInput(false);
|
||||
}}
|
||||
/>
|
||||
className={cn("flex h-10 w-full cursor-pointer items-center justify-center rounded-md bg-white")}>
|
||||
<div className="ellipsis flex w-full gap-2 truncate px-2">{getDisplayValue}</div>
|
||||
{clearable && inputType === "dropdown" ? (
|
||||
<XIcon className="shrink-0 text-slate-300" onClick={handleClear} />
|
||||
) : (
|
||||
<ChevronDownIcon className="shrink-0 text-slate-300" />
|
||||
)}
|
||||
@@ -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)) && (
|
||||
<CheckIcon 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" />}
|
||||
@@ -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)) && (
|
||||
<CheckIcon className="mr-2 h-4 w-4 shrink-0 text-slate-300" />
|
||||
)}
|
||||
{option.icon && <option.icon className="mr-2 h-5 w-5 shrink-0 text-slate-400" />}
|
||||
|
||||
@@ -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"],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user