fix: delete logicItem in case of no conditions

This commit is contained in:
Piyush Gupta
2024-09-03 10:38:13 +05:30
parent df40c0ef11
commit 7dd8bb95bd
7 changed files with 172 additions and 130 deletions

View File

@@ -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>
);
}

View File

@@ -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) => {

View File

@@ -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, {

View File

@@ -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, {

View File

@@ -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);
}
}

View File

@@ -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" />}

View File

@@ -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"],
},
};