diff --git a/apps/web/i18n.lock b/apps/web/i18n.lock index 604034c462..8ea152bdc7 100644 --- a/apps/web/i18n.lock +++ b/apps/web/i18n.lock @@ -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 diff --git a/apps/web/locales/de-DE.json b/apps/web/locales/de-DE.json index 8ea5b22422..0d4a8c8604 100644 --- a/apps/web/locales/de-DE.json +++ b/apps/web/locales/de-DE.json @@ -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.", diff --git a/apps/web/locales/en-US.json b/apps/web/locales/en-US.json index 6f9d0eabec..1174aedd9e 100644 --- a/apps/web/locales/en-US.json +++ b/apps/web/locales/en-US.json @@ -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.", diff --git a/apps/web/locales/fr-FR.json b/apps/web/locales/fr-FR.json index d650a59020..ca1c7f9eb1 100644 --- a/apps/web/locales/fr-FR.json +++ b/apps/web/locales/fr-FR.json @@ -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.", diff --git a/apps/web/locales/ja-JP.json b/apps/web/locales/ja-JP.json index 75e2b0fcc1..2f343a1b23 100644 --- a/apps/web/locales/ja-JP.json +++ b/apps/web/locales/ja-JP.json @@ -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": "言語が見つかりません。始めるには、最初のものを追加してください。", diff --git a/apps/web/locales/pt-BR.json b/apps/web/locales/pt-BR.json index b7e82e7db7..53820e414c 100644 --- a/apps/web/locales/pt-BR.json +++ b/apps/web/locales/pt-BR.json @@ -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.", diff --git a/apps/web/locales/pt-PT.json b/apps/web/locales/pt-PT.json index 789e3b4fa4..9f0167c466 100644 --- a/apps/web/locales/pt-PT.json +++ b/apps/web/locales/pt-PT.json @@ -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.", diff --git a/apps/web/locales/ro-RO.json b/apps/web/locales/ro-RO.json index 1fdcc926d6..8e1f912100 100644 --- a/apps/web/locales/ro-RO.json +++ b/apps/web/locales/ro-RO.json @@ -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.", diff --git a/apps/web/locales/zh-Hans-CN.json b/apps/web/locales/zh-Hans-CN.json index 2280d0d00b..2820d69a93 100644 --- a/apps/web/locales/zh-Hans-CN.json +++ b/apps/web/locales/zh-Hans-CN.json @@ -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": "没有找到语言。添加第一个以开始。", diff --git a/apps/web/locales/zh-Hant-TW.json b/apps/web/locales/zh-Hant-TW.json index 298bf54f70..97e133838e 100644 --- a/apps/web/locales/zh-Hant-TW.json +++ b/apps/web/locales/zh-Hant-TW.json @@ -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": "找不到語言。新增第一個語言以開始使用。", diff --git a/apps/web/modules/survey/editor/components/block-card.tsx b/apps/web/modules/survey/editor/components/block-card.tsx index 7c326a6a4a..5740c600c1 100644 --- a/apps/web/modules/survey/editor/components/block-card.tsx +++ b/apps/web/modules/survey/editor/components/block-card.tsx @@ -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 (
@@ -469,7 +477,7 @@ export const BlockCard = ({

-
e.stopPropagation()}> +
{ 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(); - - // 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 (
@@ -133,15 +115,12 @@ export function LogicEditor({ - {t("environments.surveys.edit.next_question")} + {t("environments.surveys.edit.next_block")} {fallbackOptions.map((option) => ( -
- {option.icon} - {option.label} -
+ {option.label}
))}
diff --git a/apps/web/modules/survey/editor/components/open-question-form.tsx b/apps/web/modules/survey/editor/components/open-question-form.tsx index 9512e70cbd..a467681acf 100644 --- a/apps/web/modules/survey/editor/components/open-question-form.tsx +++ b/apps/web/modules/survey/editor/components/open-question-form.tsx @@ -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"} diff --git a/apps/web/modules/survey/editor/components/questions-view.tsx b/apps/web/modules/survey/editor/components/questions-view.tsx index b9a2ec9243..5b392ea56c 100644 --- a/apps/web/modules/survey/editor/components/questions-view.tsx +++ b/apps/web/modules/survey/editor/components/questions-view.tsx @@ -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; } }; diff --git a/apps/web/modules/survey/editor/lib/shared-conditions-factory.test.ts b/apps/web/modules/survey/editor/lib/shared-conditions-factory.test.ts index c86d235578..e86cc090c1 100644 --- a/apps/web/modules/survey/editor/lib/shared-conditions-factory.test.ts +++ b/apps/web/modules/survey/editor/lib/shared-conditions-factory.test.ts @@ -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); }); }); diff --git a/apps/web/modules/survey/editor/lib/shared-conditions-factory.ts b/apps/web/modules/survey/editor/lib/shared-conditions-factory.ts index 07c331ba9c..254597d288 100644 --- a/apps/web/modules/survey/editor/lib/shared-conditions-factory.ts +++ b/apps/web/modules/survey/editor/lib/shared-conditions-factory.ts @@ -91,15 +91,9 @@ export function createSharedConditionsFactory( }; const config: TConditionsEditorConfig = { - 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), }; diff --git a/apps/web/modules/survey/editor/lib/utils.tsx b/apps/web/modules/survey/editor/lib/utils.tsx index f0d197265e..bac1da4d2b 100644 --- a/apps/web/modules/survey/editor/lib/utils.tsx +++ b/apps/web/modules/survey/editor/lib/utils.tsx @@ -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",