chore: merge with epic

This commit is contained in:
pandeymangg
2025-11-21 16:34:53 +05:30
17 changed files with 267 additions and 87 deletions

View File

@@ -1385,8 +1385,8 @@ checksums:
environments/surveys/edit/move_question_to_block: e8d7ef1e2f727921cb7f5788849492ad
environments/surveys/edit/multiply: 89a0bb629167f97750ae1645a46ced0d
environments/surveys/edit/needed_for_self_hosted_cal_com_instance: d241e72f0332177d32ce6c35070757dc
environments/surveys/edit/next_block: 53eaa5b1c9333455ab1e99bedd222ba2
environments/surveys/edit/next_button_label: e23522dd38f3eabeeccd3f48f32b73a8
environments/surveys/edit/next_question: 2e0f1ea264fb4bfcb8378b2b0cf7c18f
environments/surveys/edit/no_hidden_fields_yet_add_first_one_below: 9cc6cab3a6a42dbf835215897b5b8516
environments/surveys/edit/no_images_found_for: 90f10f4611ed7b115a49595409b66ebe
environments/surveys/edit/no_languages_found_add_first_one_to_get_started: 22d7782c8504daf693cab3cf7135d6e3

View File

@@ -1470,8 +1470,8 @@
"move_question_to_block": "Frage in Block verschieben",
"multiply": "Multiplizieren *",
"needed_for_self_hosted_cal_com_instance": "Benötigt für eine selbstgehostete Cal.com-Instanz",
"next_block": "Nächster Block",
"next_button_label": "Beschriftung der Schaltfläche \"Weiter\"",
"next_question": "Nächste Frage",
"no_hidden_fields_yet_add_first_one_below": "Noch keine versteckten Felder. Füge das erste unten hinzu.",
"no_images_found_for": "Keine Bilder gefunden für ''{query}\"",
"no_languages_found_add_first_one_to_get_started": "Keine Sprachen gefunden. Füge die erste hinzu, um loszulegen.",

View File

@@ -1470,8 +1470,8 @@
"move_question_to_block": "Move question to block",
"multiply": "Multiply *",
"needed_for_self_hosted_cal_com_instance": "Needed for a self-hosted Cal.com instance",
"next_block": "Next block",
"next_button_label": "\"Next\" button label",
"next_question": "Next question",
"no_hidden_fields_yet_add_first_one_below": "No hidden fields yet. Add the first one below.",
"no_images_found_for": "No images found for ''{query}\"",
"no_languages_found_add_first_one_to_get_started": "No languages found. Add the first one to get started.",

View File

@@ -1470,8 +1470,8 @@
"move_question_to_block": "Déplacer la question vers le bloc",
"multiply": "Multiplier *",
"needed_for_self_hosted_cal_com_instance": "Nécessaire pour une instance Cal.com auto-hébergée",
"next_block": "Bloc suivant",
"next_button_label": "Libellé du bouton «Suivant»",
"next_question": "Question suivante",
"no_hidden_fields_yet_add_first_one_below": "Aucun champ caché pour le moment. Ajoutez le premier ci-dessous.",
"no_images_found_for": "Aucune image trouvée pour ''{query}\"",
"no_languages_found_add_first_one_to_get_started": "Aucune langue trouvée. Ajoutez la première pour commencer.",

View File

@@ -1470,8 +1470,8 @@
"move_question_to_block": "質問をブロックに移動",
"multiply": "乗算 *",
"needed_for_self_hosted_cal_com_instance": "セルフホストのCal.comインスタンスに必要",
"next_block": "次のブロック",
"next_button_label": "「次へ」ボタンのラベル",
"next_question": "次の質問",
"no_hidden_fields_yet_add_first_one_below": "まだ非表示フィールドがありません。以下で最初のものを追加してください。",
"no_images_found_for": "''{query}'' の画像が見つかりません",
"no_languages_found_add_first_one_to_get_started": "言語が見つかりません。始めるには、最初のものを追加してください。",

View File

@@ -1470,8 +1470,8 @@
"move_question_to_block": "Mover pergunta para o bloco",
"multiply": "Multiplicar *",
"needed_for_self_hosted_cal_com_instance": "Necessário para uma instância auto-hospedada do Cal.com",
"next_block": "Próximo bloco",
"next_button_label": "Próximo",
"next_question": "próxima pergunta",
"no_hidden_fields_yet_add_first_one_below": "Ainda não há campos ocultos. Adicione o primeiro abaixo.",
"no_images_found_for": "Nenhuma imagem encontrada para ''{query}\"",
"no_languages_found_add_first_one_to_get_started": "Nenhum idioma encontrado. Adicione o primeiro para começar.",

View File

@@ -1470,8 +1470,8 @@
"move_question_to_block": "Mover pergunta para o bloco",
"multiply": "Multiplicar *",
"needed_for_self_hosted_cal_com_instance": "Necessário para uma instância auto-hospedada do Cal.com",
"next_block": "Bloco seguinte",
"next_button_label": "Rótulo do botão \"Seguinte\"",
"next_question": "Próxima pergunta",
"no_hidden_fields_yet_add_first_one_below": "Ainda não há campos ocultos. Adicione o primeiro abaixo.",
"no_images_found_for": "Não foram encontradas imagens para ''{query}\"",
"no_languages_found_add_first_one_to_get_started": "Nenhuma língua encontrada. Adicione a primeira para começar.",

View File

@@ -1470,8 +1470,8 @@
"move_question_to_block": "Mută întrebarea în bloc",
"multiply": "Multiplicare",
"needed_for_self_hosted_cal_com_instance": "Necesar pentru un exemplu autogăzduit Cal.com",
"next_block": "Blocul următor",
"next_button_label": "Etichetă buton \"Următorul\"",
"next_question": "Întrebarea următoare",
"no_hidden_fields_yet_add_first_one_below": "Nu există încă câmpuri ascunse. Adăugați primul mai jos.",
"no_images_found_for": "Nicio imagine găsită pentru ''{query}\"",
"no_languages_found_add_first_one_to_get_started": "Nu s-au găsit limbi. Adaugă prima pentru a începe.",

View File

@@ -1470,8 +1470,8 @@
"move_question_to_block": "将问题移动到区块",
"multiply": "乘 *",
"needed_for_self_hosted_cal_com_instance": "需要用于 自建 Cal.com 实例",
"next_block": "下一块",
"next_button_label": "\"下一步\" 按钮标签",
"next_question": "下一个问题",
"no_hidden_fields_yet_add_first_one_below": "还没有隐藏字段。 在下面添加第一个。",
"no_images_found_for": "未找到与 \"{query}\" 相关的图片",
"no_languages_found_add_first_one_to_get_started": "没有找到语言。添加第一个以开始。",

View File

@@ -1470,8 +1470,8 @@
"move_question_to_block": "將問題移至區塊",
"multiply": "乘 *",
"needed_for_self_hosted_cal_com_instance": "自行託管 Cal.com 執行個體時需要",
"next_block": "下一個區塊",
"next_button_label": "「下一步」按鈕標籤",
"next_question": "下一個問題",
"no_hidden_fields_yet_add_first_one_below": "尚無隱藏欄位。在下方新增第一個隱藏欄位。",
"no_images_found_for": "找不到「'{'query'}'」的圖片",
"no_languages_found_add_first_one_to_get_started": "找不到語言。新增第一個語言以開始使用。",

View File

@@ -425,6 +425,13 @@ export const BlockCard = ({
const blockQuestionCount = block.elements.length;
const blockQuestionCountText = blockQuestionCount === 1 ? "question" : "questions";
let blockSidebarColorClass = "";
if (isBlockInvalid) {
blockSidebarColorClass = "bg-red-400";
} else {
blockSidebarColorClass = isBlockOpen ? "bg-slate-700" : "bg-slate-400";
}
return (
<div
className={cn(
@@ -438,7 +445,8 @@ export const BlockCard = ({
{...listeners}
{...attributes}
className={cn(
isBlockInvalid ? "bg-red-400" : isBlockOpen ? "bg-slate-700" : "bg-slate-400",
// isBlockInvalid ? "bg-red-400" : isBlockOpen ? "bg-slate-700" : "bg-slate-400",
blockSidebarColorClass,
"top-0 w-10 rounded-l-lg p-2 text-center text-sm text-white hover:cursor-grab hover:bg-slate-600",
"flex flex-col items-center justify-between gap-2"
)}>
@@ -469,7 +477,7 @@ export const BlockCard = ({
</p>
</div>
</div>
<div onClick={(e) => e.stopPropagation()}>
<div>
<BlockMenu
isFirstBlock={blockIdx === 0}
isLastBlock={blockIdx === totalBlocks - 1}

View File

@@ -1,16 +1,14 @@
"use client";
import { ArrowRightIcon } from "lucide-react";
import { ReactElement, useMemo } from "react";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { TSurveyBlockLogic } from "@formbricks/types/surveys/blocks";
import { TSurveyBlock } from "@formbricks/types/surveys/blocks";
import { TSurveyBlock, TSurveyBlockLogic } from "@formbricks/types/surveys/blocks";
import { TSurvey } from "@formbricks/types/surveys/types";
import { getTextContent } from "@formbricks/types/surveys/validation";
import { recallToHeadline } from "@/lib/utils/recall";
import { LogicEditorActions } from "@/modules/survey/editor/components/logic-editor-actions";
import { LogicEditorConditions } from "@/modules/survey/editor/components/logic-editor-conditions";
import { getQuestionIconMap } from "@/modules/survey/lib/questions";
import {
Select,
SelectContent,
@@ -41,39 +39,23 @@ export function LogicEditor({
isLast,
}: LogicEditorProps) {
const { t } = useTranslation();
const QUESTIONS_ICON_MAP = getQuestionIconMap(t);
const blockLogicFallback = block.logicFallback;
const fallbackOptions = useMemo(() => {
let options: {
icon?: ReactElement;
label: string;
value: string;
}[] = [];
const blocks = localSurvey.blocks;
// Track which blocks we've already added to avoid duplicates when a block has multiple elements
const addedBlockIds = new Set<string>();
// Iterate over the elements AFTER the current block
// Add blocks AFTER the current block
for (let i = blockIdx + 1; i < blocks.length; i++) {
const currentBlock = blocks[i];
if (addedBlockIds.has(currentBlock.id)) continue;
addedBlockIds.add(currentBlock.id);
// Use the first element's headline as the block label
const firstElement = currentBlock.elements[0];
if (!firstElement) continue;
options.push({
icon: QUESTIONS_ICON_MAP[firstElement.type],
label: getTextContent(
recallToHeadline(firstElement.headline, localSurvey, false, "default").default ?? ""
),
label: currentBlock.name,
value: currentBlock.id,
});
}
@@ -92,7 +74,7 @@ export function LogicEditor({
});
return options;
}, [localSurvey, blockIdx, QUESTIONS_ICON_MAP, t]);
}, [localSurvey, blockIdx, t]);
return (
<div className="flex w-full min-w-full grow flex-col gap-4 overflow-x-auto pb-2 text-sm">
@@ -133,15 +115,12 @@ export function LogicEditor({
</SelectTrigger>
<SelectContent>
<SelectItem key="fallback_default_selection" value={"defaultSelection"}>
{t("environments.surveys.edit.next_question")}
{t("environments.surveys.edit.next_block")}
</SelectItem>
{fallbackOptions.map((option) => (
<SelectItem key={`fallback_${option.value}`} value={option.value}>
<div className="flex items-center gap-2">
{option.icon}
{option.label}
</div>
{option.label}
</SelectItem>
))}
</SelectContent>

View File

@@ -181,7 +181,7 @@ export const OpenQuestionForm = ({
},
});
}}
htmlId="charLimit"
htmlId={`charLimit-${question.id}`}
description={t("environments.surveys.edit.character_limit_toggle_description")}
childBorder
title={t("environments.surveys.edit.character_limit_toggle_title")}
@@ -238,7 +238,7 @@ export const OpenQuestionForm = ({
longAnswer: checked,
});
}}
htmlId="longAnswer"
htmlId={`longAnswer-${question.id}`}
title={t("environments.surveys.edit.long_answer")}
description={t("environments.surveys.edit.long_answer_toggle_description")}
disabled={question.inputType !== "text"}

View File

@@ -600,7 +600,7 @@ export const QuestionsView = ({
// If source block is now empty, delete it
if (sourceBlock.elements.length === 0) {
const blockIdx = updatedSurvey.blocks.findIndex((b) => b.id === sourceBlock!.id);
const blockIdx = updatedSurvey.blocks.findIndex((b) => b.id === sourceBlock.id);
if (blockIdx !== -1) {
updatedSurvey.blocks.splice(blockIdx, 1);
}
@@ -778,7 +778,6 @@ export const QuestionsView = ({
blocks.splice(destBlockIndex, 0, movedBlock);
setLocalSurvey({ ...localSurvey, blocks });
return;
}
};

View File

@@ -1,4 +1,5 @@
import { createId } from "@paralleldrive/cuid2";
import { TFunction } from "i18next";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { TSurveyQuotaLogic } from "@formbricks/types/quota";
import {
@@ -87,8 +88,12 @@ vi.mock("@/modules/survey/editor/lib/utils", () => ({
{ value: "notEquals", label: "not equals" },
{ value: "isEmpty", label: "is empty" },
]),
getDefaultOperatorForQuestion: vi.fn().mockReturnValue("equals"),
getDefaultOperatorForElement: vi.fn().mockReturnValue("equals"),
getElementOperatorOptions: vi.fn().mockReturnValue([
{ value: "equals", label: "equals" },
{ value: "notEquals", label: "not equals" },
{ value: "isEmpty", label: "is empty" },
]),
}));
vi.mock("@paralleldrive/cuid2", () => ({
@@ -169,7 +174,7 @@ describe("shared-conditions-factory", () => {
const defaultParams: SharedConditionsFactoryParams = {
survey: mockSurvey,
t: mockT,
t: mockT as unknown as TFunction,
getDefaultOperator: mockGetDefaultOperator,
};
@@ -244,15 +249,15 @@ describe("shared-conditions-factory", () => {
result.config.getLeftOperandOptions();
const { getConditionValueOptions } = await import("@/modules/survey/editor/lib/utils");
expect(getConditionValueOptions).toHaveBeenCalledWith(mockSurvey, mockT);
expect(getConditionValueOptions).toHaveBeenCalledWith(mockSurvey, mockT, undefined);
});
test("should call getConditionValueOptions with questionIdx", async () => {
const paramsWithQuestionIdx = {
const paramsWithBlockIdx = {
...defaultParams,
blockIdx: 0,
};
const result = createSharedConditionsFactory(paramsWithQuestionIdx, defaultCallbacks);
const result = createSharedConditionsFactory(paramsWithBlockIdx, defaultCallbacks);
result.config.getLeftOperandOptions();
@@ -271,15 +276,15 @@ describe("shared-conditions-factory", () => {
result.config.getValueProps(mockCondition);
const { getMatchValueProps } = await import("@/modules/survey/editor/lib/utils");
expect(getMatchValueProps).toHaveBeenCalledWith(mockCondition, mockSurvey, mockT);
expect(getMatchValueProps).toHaveBeenCalledWith(mockCondition, mockSurvey, mockT, undefined);
});
test("should call getMatchValueProps with questionIdx", async () => {
const paramsWithQuestionIdx = {
const paramsWithBlockIdx = {
...defaultParams,
blockIdx: 0,
};
const result = createSharedConditionsFactory(paramsWithQuestionIdx, defaultCallbacks);
const result = createSharedConditionsFactory(paramsWithBlockIdx, defaultCallbacks);
const mockCondition: TSingleCondition = {
id: "condition1",
leftOperand: { value: "question1", type: "element" },
@@ -301,6 +306,30 @@ describe("shared-conditions-factory", () => {
expect(mockGetDefaultOperator).toHaveBeenCalled();
});
test("should get operator options for condition", async () => {
const { getConditionOperatorOptions } = await import("@/modules/survey/editor/lib/utils");
const mockGetConditionOperatorOptions = vi.mocked(getConditionOperatorOptions);
mockGetConditionOperatorOptions.mockReturnValue([
{ value: "equals", label: "equals" },
{ value: "doesNotEqual", label: "does not equal" },
]);
const result = createSharedConditionsFactory(defaultParams, defaultCallbacks);
const mockCondition: TSingleCondition = {
id: "condition1",
leftOperand: { value: "question1", type: "element" },
operator: "equals",
};
const operators = result.config.getOperatorOptions(mockCondition);
expect(mockGetConditionOperatorOptions).toHaveBeenCalledWith(mockCondition, mockSurvey, mockT);
expect(operators).toEqual([
{ value: "equals", label: "equals" },
{ value: "doesNotEqual", label: "does not equal" },
]);
});
test("should format left operand value", async () => {
const { getFormatLeftOperandValue } = await import("@/modules/survey/editor/lib/utils");
const mockGetFormatLeftOperandValue = vi.mocked(getFormatLeftOperandValue);
@@ -361,6 +390,139 @@ describe("shared-conditions-factory", () => {
expect(mockConditionsChange).toHaveBeenCalledWith(expect.any(Function));
});
test("onUpdateCondition should correct invalid operator for element type", async () => {
const { getElementOperatorOptions } = await import("@/modules/survey/editor/lib/utils");
const mockGetElementOperatorOptions = vi.mocked(getElementOperatorOptions);
// Mock to return limited operators (e.g., only isEmpty and isNotEmpty)
mockGetElementOperatorOptions.mockReturnValue([
{ value: "isEmpty", label: "is empty" },
{ value: "isNotEmpty", label: "is not empty" },
]);
const result = createSharedConditionsFactory(defaultParams, defaultCallbacks);
const resourceId = "condition1";
const updates = {
leftOperand: {
value: "question1",
type: "element" as const,
},
operator: "equals" as TSurveyLogicConditionsOperator, // Invalid operator for this element
};
result.callbacks.onUpdateCondition(resourceId, updates);
expect(mockConditionsChange).toHaveBeenCalledWith(expect.any(Function));
// Get the updater function that was called
const updater = mockConditionsChange.mock.calls[0][0] as (c: TConditionGroup) => TConditionGroup;
const mockConditions: TConditionGroup = {
id: "root",
connector: "and",
conditions: [
{
id: "condition1",
leftOperand: { value: "oldQuestion", type: "element" },
operator: "equals",
},
],
};
updater(structuredClone(mockConditions));
// Verify the operator was validated
expect(mockGetElementOperatorOptions).toHaveBeenCalled();
});
test("onUpdateCondition should handle update with valid operator", async () => {
const { getElementOperatorOptions } = await import("@/modules/survey/editor/lib/utils");
const mockGetElementOperatorOptions = vi.mocked(getElementOperatorOptions);
// Mock to return operators that include the one being set
mockGetElementOperatorOptions.mockReturnValue([
{ value: "equals", label: "equals" },
{ value: "doesNotEqual", label: "does not equal" },
]);
const result = createSharedConditionsFactory(defaultParams, defaultCallbacks);
const resourceId = "condition1";
const updates = {
leftOperand: {
value: "question1",
type: "element" as const,
},
operator: "equals" as TSurveyLogicConditionsOperator, // Valid operator
};
result.callbacks.onUpdateCondition(resourceId, updates);
expect(mockConditionsChange).toHaveBeenCalledWith(expect.any(Function));
});
test("onUpdateCondition should handle update without leftOperand", () => {
const result = createSharedConditionsFactory(defaultParams, defaultCallbacks);
const resourceId = "condition1";
const updates = {
operator: "equals" as TSurveyLogicConditionsOperator,
};
result.callbacks.onUpdateCondition(resourceId, updates);
expect(mockConditionsChange).toHaveBeenCalledWith(expect.any(Function));
});
test("onUpdateCondition should handle update without operator", () => {
const result = createSharedConditionsFactory(defaultParams, defaultCallbacks);
const resourceId = "condition1";
const updates = {
leftOperand: {
value: "question1",
type: "element" as const,
},
};
result.callbacks.onUpdateCondition(resourceId, updates);
expect(mockConditionsChange).toHaveBeenCalledWith(expect.any(Function));
});
test("onUpdateCondition should handle non-question leftOperand type", () => {
const result = createSharedConditionsFactory(defaultParams, defaultCallbacks);
const resourceId = "condition1";
const updates = {
leftOperand: {
value: "variable1",
type: "variable" as const,
},
operator: "equals" as TSurveyLogicConditionsOperator,
};
result.callbacks.onUpdateCondition(resourceId, updates);
expect(mockConditionsChange).toHaveBeenCalledWith(expect.any(Function));
});
test("onUpdateCondition should handle element not found", async () => {
const { getElementOperatorOptions } = await import("@/modules/survey/editor/lib/utils");
const mockGetElementOperatorOptions = vi.mocked(getElementOperatorOptions);
const result = createSharedConditionsFactory(defaultParams, defaultCallbacks);
const resourceId = "condition1";
const updates = {
leftOperand: {
value: "non-existent-question",
type: "element" as const,
},
operator: "equals" as TSurveyLogicConditionsOperator,
};
result.callbacks.onUpdateCondition(resourceId, updates);
expect(mockConditionsChange).toHaveBeenCalledWith(expect.any(Function));
// Should not call getElementOperatorOptions if element not found
expect(mockGetElementOperatorOptions).not.toHaveBeenCalled();
});
test("onToggleGroupConnector should toggle group connector", () => {
const result = createSharedConditionsFactory(defaultParams, defaultCallbacks);
const groupId = "group1";
@@ -368,6 +530,21 @@ describe("shared-conditions-factory", () => {
result.callbacks.onToggleGroupConnector(groupId);
expect(mockConditionsChange).toHaveBeenCalledWith(expect.any(Function));
// Execute the updater function to ensure it runs properly
const updater = mockConditionsChange.mock.calls[0][0] as (c: TConditionGroup) => TConditionGroup;
const mockConditions: TConditionGroup = {
id: "root",
connector: "and",
conditions: [
{
id: "group1",
connector: "and",
conditions: [],
},
],
};
updater(mockConditions);
});
test("onCreateGroup should create group when includeCreateGroup is true", () => {
@@ -381,6 +558,21 @@ describe("shared-conditions-factory", () => {
result.callbacks.onCreateGroup!(resourceId);
expect(mockConditionsChange).toHaveBeenCalledWith(expect.any(Function));
// Execute the updater function to ensure it runs properly
const updater = mockConditionsChange.mock.calls[0][0] as (c: TConditionGroup) => TConditionGroup;
const mockConditions: TConditionGroup = {
id: "root",
connector: "and",
conditions: [
{
id: "condition1",
leftOperand: { value: "question1", type: "question" },
operator: "equals",
},
],
};
updater(mockConditions);
});
});

View File

@@ -91,15 +91,9 @@ export function createSharedConditionsFactory(
};
const config: TConditionsEditorConfig<TSingleCondition> = {
getLeftOperandOptions: () =>
blockIdx !== undefined
? getConditionValueOptions(survey, t, blockIdx)
: getConditionValueOptions(survey, t),
getLeftOperandOptions: () => getConditionValueOptions(survey, t, blockIdx),
getOperatorOptions: (condition) => getConditionOperatorOptions(condition, survey, t),
getValueProps: (condition) =>
blockIdx !== undefined
? getMatchValueProps(condition, survey, t, blockIdx)
: getMatchValueProps(condition, survey, t),
getValueProps: (condition) => getMatchValueProps(condition, survey, t, blockIdx),
getDefaultOperator,
formatLeftOperandValue: (condition) => getFormatLeftOperandValue(condition, survey),
};

View File

@@ -26,7 +26,7 @@ import { isConditionGroup } from "@/lib/surveyLogic/utils";
import { recallToHeadline } from "@/lib/utils/recall";
import { findElementLocation } from "@/modules/survey/editor/lib/blocks";
import { getElementsFromBlocks } from "@/modules/survey/lib/client-utils";
import { getQuestionTypes } from "@/modules/survey/lib/questions";
import { getQuestionTypes, getTSurveyQuestionTypeEnumName } from "@/modules/survey/lib/questions";
import { TComboboxGroupedOption, TComboboxOption } from "@/modules/ui/components/input-combo-box";
import { TLogicRuleOption, getLogicRules } from "./logic-rule-engine";
@@ -106,6 +106,23 @@ const getQuestionIconMapping = (t: TFunction) =>
{}
);
const getElementHeadline = (
localSurvey: TSurvey,
element: TSurveyElement,
languageCode: string,
t: TFunction
): string => {
const headlineData = recallToHeadline(element.headline, localSurvey, false, languageCode);
const headlineText = headlineData[languageCode];
if (headlineText) {
const textContent = getTextContent(headlineText);
if (textContent.length > 0) {
return textContent;
}
}
return getTSurveyQuestionTypeEnumName(element.type, t) ?? "";
};
export const getConditionValueOptions = (
localSurvey: TSurvey,
t: TFunction,
@@ -117,20 +134,18 @@ export const getConditionValueOptions = (
// If blockIdx is provided, get elements from current block and all previous blocks
// Otherwise, get all elements from all blocks
const allElements =
blockIdx !== undefined
? localSurvey.blocks
.slice(0, blockIdx + 1) // Include blocks from 0 to blockIdx (inclusive)
.flatMap((block) => block.elements)
: getElementsFromBlocks(localSurvey.blocks);
blockIdx === undefined
? getElementsFromBlocks(localSurvey.blocks)
: localSurvey.blocks.slice(0, blockIdx + 1).flatMap((block) => block.elements);
const groupedOptions: TComboboxGroupedOption[] = [];
const elementOptions: TComboboxOption[] = [];
allElements.forEach((element) => {
if (element.type === TSurveyElementTypeEnum.Matrix) {
const elementHeadline = getElementHeadline(localSurvey, element, "default", t);
// Rows submenu
const processedHeadline = recallToHeadline(element.headline, localSurvey, false, "default");
const elementHeadline = getTextContent(processedHeadline.default ?? "");
const rows = element.rows.map((row, rowIdx) => {
const processedLabel = recallToHeadline(row.label, localSurvey, false, "default");
return {
@@ -169,9 +184,7 @@ export const getConditionValueOptions = (
} else {
elementOptions.push({
icon: getQuestionIconMapping(t)[element.type],
label: getTextContent(
recallToHeadline(element.headline, localSurvey, false, "default").default ?? ""
),
label: getElementHeadline(localSurvey, element, "default", t),
value: element.id,
meta: {
type: "element",
@@ -358,11 +371,11 @@ export const getMatchValueProps = (
// If blockIdx is provided, get elements from current block and all previous blocks
// Otherwise, get all elements from all blocks
let elements =
blockIdx !== undefined
? localSurvey.blocks
blockIdx === undefined
? getElementsFromBlocks(localSurvey.blocks)
: localSurvey.blocks
.slice(0, blockIdx + 1) // Include blocks from 0 to blockIdx (inclusive)
.flatMap((block) => block.elements)
: getElementsFromBlocks(localSurvey.blocks);
.flatMap((block) => block.elements);
let variables = localSurvey.variables ?? [];
let hiddenFields = localSurvey.hiddenFields?.fieldIds ?? [];
@@ -413,7 +426,7 @@ export const getMatchValueProps = (
const variableOptions = variables
.filter((variable) =>
selectedElement.inputType !== "number" ? variable.type === "text" : variable.type === "number"
selectedElement.inputType === "number" ? variable.type === "number" : variable.type === "text"
)
.map((variable) => {
return {
@@ -715,10 +728,9 @@ export const getMatchValueProps = (
const allowedElements = elements.filter((element) => allowedElementTypes.includes(element.type));
const elementOptions = allowedElements.map((element) => {
const processedHeadline = recallToHeadline(element.headline, localSurvey, false, "default");
return {
icon: getQuestionIconMapping(t)[element.type],
label: getTextContent(processedHeadline.default ?? ""),
label: getElementHeadline(localSurvey, element, "default", t),
value: element.id,
meta: {
type: "element",
@@ -790,10 +802,9 @@ export const getMatchValueProps = (
);
const elementOptions = allowedElements.map((element) => {
const processedHeadline = recallToHeadline(element.headline, localSurvey, false, "default");
return {
icon: getQuestionIconMapping(t)[element.type],
label: getTextContent(processedHeadline.default ?? ""),
label: getElementHeadline(localSurvey, element, "default", t),
value: element.id,
meta: {
type: "element",
@@ -871,10 +882,9 @@ export const getMatchValueProps = (
const allowedElements = elements.filter((element) => allowedElementTypes.includes(element.type));
const elementOptions = allowedElements.map((element) => {
const processedHeadline = recallToHeadline(element.headline, localSurvey, false, "default");
return {
icon: getQuestionIconMapping(t)[element.type],
label: getTextContent(processedHeadline.default ?? ""),
label: getElementHeadline(localSurvey, element, "default", t),
value: element.id,
meta: {
type: "element",
@@ -967,11 +977,10 @@ export const getActionTargetOptions = (
// Return element IDs for requireAnswer
return nonRequiredElements.map((element) => {
const processedHeadline = recallToHeadline(element.headline, localSurvey, false, "default");
return {
icon: getQuestionIconMapping(t)[element.type],
label: getTextContent(processedHeadline.default ?? ""),
value: element.id, // Element ID
label: getElementHeadline(localSurvey, element, "default", t),
value: element.id,
};
});
}
@@ -1114,10 +1123,9 @@ export const getActionValueOptions = (
);
const elementOptions = allowedElements.map((element) => {
const processedHeadline = recallToHeadline(element.headline, localSurvey, false, "default");
return {
icon: getQuestionIconMapping(t)[element.type],
label: getTextContent(processedHeadline.default ?? ""),
label: getElementHeadline(localSurvey, element, "default", t),
value: element.id,
meta: {
type: "element",