Browse Source

feat: implement marriage profile normalization logic and schema-based UI navigation adapters

master
Muhammad A. Ghorbani 2 weeks ago
parent
commit
b7ca24027b
  1. 10
      src/app/questions-list/[slug]/question-detail-client.tsx
  2. 2
      src/app/questions-list/questions-list-client.tsx
  3. 99
      src/components/Componentes/question-sheet.test.tsx
  4. 111
      src/components/Componentes/question-sheet.tsx
  5. 24
      src/data/languages.test.ts
  6. 123
      src/data/languages.ts
  7. 2
      src/lib/marriage-profile-contract.ts
  8. 16
      src/lib/schema-adapter.ts

10
src/app/questions-list/[slug]/question-detail-client.tsx

@ -520,7 +520,7 @@ export default function QuestionDetailClient({
<NavigationButton <NavigationButton
className="shrink-0" className="shrink-0"
variant="transparent" variant="transparent"
icon="close"
icon="back"
iconLabel={closeLabel} iconLabel={closeLabel}
onClick={handleExit} onClick={handleExit}
/> />
@ -565,7 +565,7 @@ export default function QuestionDetailClient({
<NavigationButton <NavigationButton
className="shrink-0" className="shrink-0"
variant="transparent" variant="transparent"
icon="close"
icon="back"
iconLabel={closeLabel} iconLabel={closeLabel}
onClick={handleExit} onClick={handleExit}
/> />
@ -601,7 +601,7 @@ export default function QuestionDetailClient({
<NavigationButton <NavigationButton
className="shrink-0" className="shrink-0"
variant="transparent" variant="transparent"
icon="close"
icon="back"
iconLabel={closeLabel} iconLabel={closeLabel}
onClick={handleExit} onClick={handleExit}
/> />
@ -780,7 +780,7 @@ export default function QuestionDetailClient({
<NavigationButton <NavigationButton
className="shrink-0" className="shrink-0"
variant="transparent" variant="transparent"
icon="close"
icon="back"
iconLabel={closeLabel} iconLabel={closeLabel}
onClick={handleExit} onClick={handleExit}
/> />
@ -961,7 +961,7 @@ export default function QuestionDetailClient({
<QuestionExitNavigationButton <QuestionExitNavigationButton
className="shrink-0" className="shrink-0"
variant="transparent" variant="transparent"
icon="close"
icon="back"
iconLabel={closeLabel} iconLabel={closeLabel}
exitHref={questionsListHref} exitHref={questionsListHref}
onExit={handleExit} onExit={handleExit}

2
src/app/questions-list/questions-list-client.tsx

@ -957,7 +957,7 @@ export default function QuestionsListClient({
<div className="mt-4"> <div className="mt-4">
<RequiredStepsCard <RequiredStepsCard
completed={displayedRequiredSections} completed={displayedRequiredSections}
total={REQUIRED_PROFILE_SECTION_COUNT}
total={requiredQuestionListItems.length || REQUIRED_PROFILE_SECTION_COUNT}
/> />
</div> </div>

99
src/components/Componentes/question-sheet.test.tsx

@ -744,6 +744,105 @@ describe("QuestionSheet component", () => {
expect(screen.getByRole("button", { name: "Арабский" })).toBeDefined(); expect(screen.getByRole("button", { name: "Арабский" })).toBeDefined();
expect(screen.getByRole("button", { name: "Русский" })).toBeDefined(); expect(screen.getByRole("button", { name: "Русский" })).toBeDefined();
}); });
it("renders search bar and full list of languages for mother_tongue even with initial partial options", async () => {
const { useI18n } = await import("@/translations/provider");
(useI18n as any).mockReturnValue({
locale: "fa",
dictionary: { "Mother Tongue": "زبان مادری", Confirm: "تایید" },
});
const languageQuestion = {
id: "personal_identity.mother_tongue",
title: "Native Language / Mother Tongue",
type: "dropdown",
order: 1,
required: true,
isVisible: true,
description: "",
tooltip: "",
extras: { placeHolder: "انتخاب کنید", noSearch: true },
ui_config: { noSearch: true },
options: [
{ id: "opt_fa", value: "persian", label: "Persian (Farsi)", order: 1 },
{ id: "opt_en", value: "english", label: "English", order: 2 },
{ id: "opt_other", value: "other", label: "Other", order: 3 },
],
} as QuestionField;
render(
<QueryClientProvider client={queryClient}>
<QuestionAnswersProvider slug="test" questions={[languageQuestion]}>
<QuestionSheet question={languageQuestion} />
</QuestionAnswersProvider>
</QueryClientProvider>,
);
fireEvent.click(screen.getByRole("button"));
// Verify search input is present
const searchInput = screen.getByPlaceholderText("جستجو...");
expect(searchInput).toBeDefined();
// Verify options have full languages list (e.g. آلمانی, فرانسوی, etc.)
expect(screen.getByRole("button", { name: "آلمانی" })).toBeDefined();
expect(screen.getByRole("button", { name: "فرانسوی" })).toBeDefined();
expect(screen.getByRole("button", { name: "فارسی" })).toBeDefined();
// Test search filtering
fireEvent.change(searchInput, { target: { value: "آلمان" } });
expect(screen.getByRole("button", { name: "آلمانی" })).toBeDefined();
expect(screen.queryByRole("button", { name: "فرانسوی" })).toBeNull();
});
it("renders search bar and filters country options for current_nationality_citizenship", async () => {
const { useI18n } = await import("@/translations/provider");
(useI18n as any).mockReturnValue({
locale: "en",
dictionary: { Confirm: "Confirm" },
});
const nationalityQuestion = {
id: "personal_identity.current_nationality_citizenship",
title: "Current Nationality / Citizenship",
type: "dropdown",
order: 2,
required: true,
isVisible: true,
description: "",
tooltip: "",
extras: { placeHolder: "Select one option", noSearch: true },
ui_config: { noSearch: true },
options: [
{ id: "opt_af", value: "afghanistan", label: "Afghanistan", order: 1 },
{ id: "opt_al", value: "albania", label: "Albania", order: 2 },
{ id: "opt_dz", value: "algeria", label: "Algeria", order: 3 },
],
} as QuestionField;
render(
<QueryClientProvider client={queryClient}>
<QuestionAnswersProvider slug="test" questions={[nationalityQuestion]}>
<QuestionSheet question={nationalityQuestion} />
</QuestionAnswersProvider>
</QueryClientProvider>,
);
fireEvent.click(screen.getByRole("button"));
// Verify search input is present
const searchInput = screen.getByPlaceholderText("Search...");
expect(searchInput).toBeDefined();
// Verify country options are rendered
expect(screen.getByRole("button", { name: "Afghanistan" })).toBeDefined();
expect(screen.getByRole("button", { name: "Albania" })).toBeDefined();
// Test search filtering by country name
fireEvent.change(searchInput, { target: { value: "Albania" } });
expect(screen.getByRole("button", { name: "Albania" })).toBeDefined();
expect(screen.queryByRole("button", { name: "Afghanistan" })).toBeNull();
});
}); });

111
src/components/Componentes/question-sheet.tsx

@ -9,7 +9,12 @@ import {
normalizeSearchString, normalizeSearchString,
getCountrySearchKeywords, getCountrySearchKeywords,
} from "@/data/countries"; } from "@/data/countries";
import { LANGUAGES_EN, LANGUAGES_FA, resolveLanguageName } from "@/data/languages";
import {
LANGUAGES_EN,
LANGUAGES_FA,
resolveLanguageName,
getLanguageSearchKeywords,
} from "@/data/languages";
import type { QuestionField } from "@/lib/schema-adapter"; import type { QuestionField } from "@/lib/schema-adapter";
import { useI18n } from "@/translations/provider"; import { useI18n } from "@/translations/provider";
import { Button } from "./button"; import { Button } from "./button";
@ -21,6 +26,7 @@ import { useSheetScrollLock } from "./use-sheet-scroll-lock";
const EXIT_ANIMATION_MS = 200; const EXIT_ANIMATION_MS = 200;
const EMPTY_ARRAY: string[] = []; const EMPTY_ARRAY: string[] = [];
const COMPACT_OPTIONS_MAX = 6;
export type QuestionSheetProps = { export type QuestionSheetProps = {
question: QuestionField; question: QuestionField;
@ -122,32 +128,47 @@ export function QuestionSheet({ question, disabled }: QuestionSheetProps) {
question.id?.toLowerCase().includes("residence_status") || question.id?.toLowerCase().includes("residence_status") ||
question.id?.toLowerCase().includes("status") || question.id?.toLowerCase().includes("status") ||
question.id?.toLowerCase().includes("responsibility") || question.id?.toLowerCase().includes("responsibility") ||
question.extras?.noSearch === true ||
question.ui_config?.noSearch === true;
question.id?.toLowerCase().includes("difference") ||
question.id?.toLowerCase().includes("impact");
const isLanguageQuestion =
!isExcludedFromAutoDatasets &&
(question.ui_config?.dataset === "languages" ||
const isLanguageQuestion = Boolean(
question.ui_config?.dataset === "languages" ||
question.id?.endsWith(".mother_tongue") || question.id?.endsWith(".mother_tongue") ||
question.id?.endsWith(".native_language") || question.id?.endsWith(".native_language") ||
question.id?.endsWith(".other_languages") || question.id?.endsWith(".other_languages") ||
(Boolean(question.title?.toLowerCase().includes("language")) &&
!question.options?.length));
question.id?.endsWith(".languages") ||
question.id?.endsWith(".language") ||
(!question.id?.includes("impact") &&
!question.id?.includes("difference") &&
Boolean(
question.title?.toLowerCase().includes("mother tongue") ||
question.title?.toLowerCase().includes("native language") ||
question.title?.toLowerCase() === "language" ||
question.title?.toLowerCase() === "languages" ||
question.title?.includes("زبان مادری") ||
question.title?.includes("زبان‌های مسلط") ||
question.title?.includes("زبان های مسلط")
))
);
const isCountryQuestion =
const isCountryQuestion = Boolean(
!isExcludedFromAutoDatasets && !isExcludedFromAutoDatasets &&
(question.ui_config?.dataset === "countries" || (question.ui_config?.dataset === "countries" ||
question.ui_config?.dataset === "nationalities" || question.ui_config?.dataset === "nationalities" ||
question.id?.endsWith(".nationality") ||
question.id?.endsWith(".citizenship") ||
question.id?.endsWith(".second_nationality") ||
(Boolean(
question.id?.includes("nationality") ||
question.id?.includes("citizenship") ||
question.id?.endsWith(".birthplace") || question.id?.endsWith(".birthplace") ||
question.id?.endsWith(".current_residence") || question.id?.endsWith(".current_residence") ||
question.id?.endsWith(".country") ||
Boolean(
question.title?.toLowerCase().includes("nationality") || question.title?.toLowerCase().includes("nationality") ||
question.title?.toLowerCase().includes("citizenship"),
) &&
!question.options?.length));
question.title?.toLowerCase().includes("citizenship") ||
question.title?.toLowerCase().includes("country") ||
question.title?.includes("تابعیت") ||
question.title?.includes("ملیت") ||
question.title?.includes("کشور")
))
);
const options = useMemo(() => { const options = useMemo(() => {
const rawOptions = question.options || []; const rawOptions = question.options || [];
@ -165,6 +186,7 @@ export function QuestionSheet({ question, disabled }: QuestionSheetProps) {
const mergedOptions: typeof rawOptions = []; const mergedOptions: typeof rawOptions = [];
const seenIds = new Set<string>(); const seenIds = new Set<string>();
const seenLabels = new Set<string>();
LANGUAGES_EN.forEach((enLang, idx) => { LANGUAGES_EN.forEach((enLang, idx) => {
const displayLabel = resolveLanguageName(enLang, locale); const displayLabel = resolveLanguageName(enLang, locale);
@ -181,8 +203,10 @@ export function QuestionSheet({ question, disabled }: QuestionSheetProps) {
existingById.get(generatedId.toLowerCase()); existingById.get(generatedId.toLowerCase());
const finalId = existing?.id || generatedId; const finalId = existing?.id || generatedId;
if (seenIds.has(finalId)) return;
const normLabel = displayLabel.toLowerCase().trim();
if (seenIds.has(finalId) || seenLabels.has(normLabel)) return;
seenIds.add(finalId); seenIds.add(finalId);
seenLabels.add(normLabel);
mergedOptions.push({ mergedOptions.push({
id: finalId, id: finalId,
@ -193,12 +217,27 @@ export function QuestionSheet({ question, disabled }: QuestionSheetProps) {
}); });
rawOptions.forEach((opt) => { rawOptions.forEach((opt) => {
if (!seenIds.has(opt.id)) {
const normLabel = opt.label.toLowerCase().trim();
if (!seenIds.has(opt.id) && !seenLabels.has(normLabel)) {
seenIds.add(opt.id); seenIds.add(opt.id);
seenLabels.add(normLabel);
mergedOptions.push(opt); mergedOptions.push(opt);
} }
}); });
// Keep "Other" / "سایر" at the very end
const otherIndex = mergedOptions.findIndex(
(opt) =>
opt.value === "other" ||
opt.id.endsWith(".other") ||
opt.label.toLowerCase() === "other" ||
opt.label === "سایر",
);
if (otherIndex !== -1 && otherIndex !== mergedOptions.length - 1) {
const [otherOpt] = mergedOptions.splice(otherIndex, 1);
mergedOptions.push(otherOpt);
}
return mergedOptions; return mergedOptions;
} }
@ -259,13 +298,16 @@ export function QuestionSheet({ question, disabled }: QuestionSheetProps) {
}, [question, isLanguageQuestion, isCountryQuestion, locale, t]); }, [question, isLanguageQuestion, isCountryQuestion, locale, t]);
const noSearch = Boolean( const noSearch = Boolean(
!isLanguageQuestion &&
!isCountryQuestion &&
options.length <= COMPACT_OPTIONS_MAX &&
(
question.extras?.noSearch === true || question.extras?.noSearch === true ||
question.ui_config?.noSearch === true || question.ui_config?.noSearch === true ||
question.id?.toLowerCase().includes("responsibility"),
question.id?.toLowerCase().includes("responsibility")
)
); );
const COMPACT_OPTIONS_MAX = 6;
const isCompact = options.length <= COMPACT_OPTIONS_MAX || noSearch; const isCompact = options.length <= COMPACT_OPTIONS_MAX || noSearch;
const showSearch = !noSearch && options.length > COMPACT_OPTIONS_MAX; const showSearch = !noSearch && options.length > COMPACT_OPTIONS_MAX;
@ -299,6 +341,13 @@ export function QuestionSheet({ question, disabled }: QuestionSheetProps) {
) { ) {
return true; return true;
} }
if (isLanguageQuestion) {
const labelKeywords = getLanguageSearchKeywords(option.label, locale);
if (labelKeywords.some((k) => k.includes(normQ))) return true;
const valKeywords = getLanguageSearchKeywords(String(option.value), locale);
if (valKeywords.some((k) => k.includes(normQ))) return true;
}
if (isCountryQuestion) { if (isCountryQuestion) {
const labelKeywords = getCountrySearchKeywords(option.label, locale); const labelKeywords = getCountrySearchKeywords(option.label, locale);
if (labelKeywords.some((k) => k.includes(normQ))) return true; if (labelKeywords.some((k) => k.includes(normQ))) return true;
@ -308,7 +357,7 @@ export function QuestionSheet({ question, disabled }: QuestionSheetProps) {
} }
return false; return false;
}); });
}, [options, searchQuery, isCountryQuestion, locale]);
}, [options, searchQuery, isLanguageQuestion, isCountryQuestion, locale]);
const getCleanLabel = (optId: string) => { const getCleanLabel = (optId: string) => {
const normalized = String(optId || "").toLowerCase().trim(); const normalized = String(optId || "").toLowerCase().trim();
@ -317,9 +366,16 @@ export function QuestionSheet({ question, disabled }: QuestionSheetProps) {
o.id === optId || o.id === optId ||
o.id.toLowerCase() === normalized || o.id.toLowerCase() === normalized ||
String(o.value).toLowerCase() === normalized || String(o.value).toLowerCase() === normalized ||
o.label.toLowerCase().trim() === normalized ||
o.id.endsWith(`.${normalized}`), o.id.endsWith(`.${normalized}`),
); );
if (!opt) return optId;
if (!opt) {
if (isLanguageQuestion) {
const resolved = resolveLanguageName(optId, locale);
if (resolved && resolved !== optId) return resolved;
}
return optId;
}
return opt.label.split(" - ")[0]; return opt.label.split(" - ")[0];
}; };
@ -593,8 +649,13 @@ export function QuestionSheet({ question, disabled }: QuestionSheetProps) {
{filteredOptions.length > 0 ? ( {filteredOptions.length > 0 ? (
filteredOptions.map((option) => { filteredOptions.map((option) => {
const isSelected = isMulti const isSelected = isMulti
? localSelectedList.includes(option.id)
: singleValue === option.id;
? localSelectedList.includes(option.id) ||
localSelectedList.includes(option.value) ||
localSelectedList.includes(option.id.split(".").pop() || "")
: singleValue === option.id ||
singleValue === option.value ||
singleValue === option.id.split(".").pop() ||
option.id.endsWith(`.${singleValue}`);
const isOptionDisabled = const isOptionDisabled =
isMulti && isMulti &&

24
src/data/languages.test.ts

@ -5,6 +5,8 @@ import {
getLanguageCode, getLanguageCode,
getLanguageList, getLanguageList,
resolveLanguageName, resolveLanguageName,
getLanguageSearchKeywords,
filterLanguageOptions,
} from "./languages"; } from "./languages";
describe("languages data and localization", () => { describe("languages data and localization", () => {
@ -56,4 +58,26 @@ describe("languages data and localization", () => {
const listEn = getLanguageList("en"); const listEn = getLanguageList("en");
expect(listEn[0]).toBe("Afrikaans"); expect(listEn[0]).toBe("Afrikaans");
}); });
it("extracts language search keywords and filters options correctly", () => {
const faKeywords = getLanguageSearchKeywords("Persian (Farsi)", "fa");
expect(faKeywords).toContain("farsi");
expect(faKeywords).toContain("فارسی");
const options = ["Persian (Farsi)", "English", "Arabic", "German", "Other"];
// Search in Persian
expect(filterLanguageOptions(options, "فارسی", "fa")).toEqual(["Persian (Farsi)"]);
expect(filterLanguageOptions(options, "انگلیسی", "fa")).toEqual(["English"]);
expect(filterLanguageOptions(options, "آلمانی", "fa")).toEqual(["German"]);
// Search in English
expect(filterLanguageOptions(options, "persian", "en")).toEqual(["Persian (Farsi)"]);
expect(filterLanguageOptions(options, "farsi", "en")).toEqual(["Persian (Farsi)"]);
expect(filterLanguageOptions(options, "ger", "en")).toEqual(["German"]);
// Search for other
expect(filterLanguageOptions(options, "سایر", "fa")).toEqual(["Other"]);
expect(filterLanguageOptions(options, "other", "en")).toEqual(["Other"]);
});
}); });

123
src/data/languages.ts

@ -48,7 +48,6 @@ export const LANGUAGES_EN = [
"Nepali", "Nepali",
"Norwegian", "Norwegian",
"Pashto", "Pashto",
"Persian",
"Persian (Farsi)", "Persian (Farsi)",
"Polish", "Polish",
"Portuguese", "Portuguese",
@ -405,3 +404,125 @@ export function getLanguageList(locale: string = "en"): string[] {
return LANGUAGES_EN.map((enLang) => resolveLanguageName(enLang, locale)); return LANGUAGES_EN.map((enLang) => resolveLanguageName(enLang, locale));
} }
export const LANGUAGE_ALIASES: Record<string, string[]> = {
fa: ["persian", "farsi", "فارسی", "پارسی", "فارسی دری", "دری", "تاجیکی"],
en: ["english", "انگلیسی", "اینگلیسی", "انگلیش"],
ar: ["arabic", "عربی", "عربى"],
tr: ["turkish", "ترکی", "ترکی استانبولی", "turkce", "türkçe"],
ur: ["urdu", "اردو", "اردو رومن", "roman urdu"],
fr: ["french", "فرانسوی", "فرانسه", "francais", "français"],
de: ["german", "آلمانی", "deutsch"],
es: ["spanish", "اسپانیایی", "espanol", "español"],
ru: ["russian", "روسی", "русский"],
zh: ["chinese", "چینی", "ماندارین", "mandarin"],
hi: ["hindi", "هندی"],
az: ["azerbaijani", "azeri", "آذربایجانی", "ترکی آذربایجانی", "ترکی آذری"],
ku: ["kurdish", "کردی", "کوردی", "سورانی", "کورمانجی"],
bal: ["balochi", "بلوچی"],
ps: ["pashto", "پشتو", "پختو"],
it: ["italian", "ایتالیایی"],
ja: ["japanese", "ژاپنی"],
ko: ["korean", "کره ای", "کره‌ای"],
pt: ["portuguese", "پرتغالی"],
nl: ["dutch", "هلندی"],
sv: ["swedish", "سوئدی"],
pl: ["polish", "لهستانی"],
other: ["other", "سایر", "دیگر", "سایر زبان‌ها", "سایر زبان ها"],
};
export function normalizeLanguageSearch(str: string): string {
return str
.toLowerCase()
.trim()
.replace(/[\u064B-\u065F\u0670]/g, "")
.replace(/[\u200B-\u200D\uFEFF]/g, "")
.replace(/ي/g, "ی")
.replace(/ك/g, "ک")
.replace(/[\s\-_.,/\\()]+/g, " ");
}
export function getLanguageSearchKeywords(
langOrCode: string | undefined | null,
locale?: string,
): string[] {
if (!langOrCode) return [];
const keywords = new Set<string>();
const addTerm = (term: string | undefined | null) => {
if (!term) return;
const norm = normalizeLanguageSearch(term);
if (norm) keywords.add(norm);
};
addTerm(langOrCode);
const code = getLanguageCode(langOrCode);
if (code) {
addTerm(code);
const aliases = LANGUAGE_ALIASES[code] || [];
aliases.forEach(addTerm);
const enKey = Object.keys(LANGUAGE_EN_TO_CODE).find(
(k) => LANGUAGE_EN_TO_CODE[k] === code,
);
if (enKey) {
addTerm(enKey);
if (LANGUAGE_EN_TO_FA[enKey]) {
addTerm(LANGUAGE_EN_TO_FA[enKey]);
}
}
try {
const enNames = new Intl.DisplayNames(["en"], { type: "language" });
addTerm(enNames.of(code));
} catch {}
try {
const faNames = new Intl.DisplayNames(["fa"], { type: "language" });
addTerm(faNames.of(code));
} catch {}
if (locale && locale !== "en" && locale !== "fa") {
try {
const locNames = new Intl.DisplayNames([locale], { type: "language" });
addTerm(locNames.of(code));
} catch {}
}
}
const trimmedLower = langOrCode.toLowerCase().trim();
if (
trimmedLower === "other" ||
trimmedLower === "سایر" ||
code === "other"
) {
const otherAliases = ["other", "سایر", "دیگر", "دیگری", "غیره"];
otherAliases.forEach(addTerm);
}
return Array.from(keywords);
}
export function filterLanguageOptions(
options: string[],
searchQuery: string,
locale?: string,
): string[] {
const rawQ = searchQuery.trim();
if (!rawQ) return options;
const normQ = normalizeLanguageSearch(rawQ);
const lowerQ = rawQ.toLowerCase();
return options.filter((option) => {
if (
option.toLowerCase().includes(lowerQ) ||
normalizeLanguageSearch(option).includes(normQ)
) {
return true;
}
const keywords = getLanguageSearchKeywords(option, locale);
return keywords.some((k) => k.includes(normQ));
});
}

2
src/lib/marriage-profile-contract.ts

@ -7,7 +7,7 @@ import type {
MarriageRecommendedPlan, MarriageRecommendedPlan,
} from "@/hooks/marriage/types"; } from "@/hooks/marriage/types";
export const REQUIRED_PROFILE_SECTION_COUNT = 10;
export const REQUIRED_PROFILE_SECTION_COUNT = 9;
/** /**
* Normalizes a single MarriageField from various potential backend or Flutter bridge formats. * Normalizes a single MarriageField from various potential backend or Flutter bridge formats.

16
src/lib/schema-adapter.ts

@ -224,10 +224,24 @@ export function mapBackendQuestionToFrontend(
if (!uiConfig.picker_type) uiConfig.picker_type = uiConfig.source || "file_system"; if (!uiConfig.picker_type) uiConfig.picker_type = uiConfig.source || "file_system";
} }
let questionType = bq.type;
if (
questionType === "text" &&
(bq.id?.includes("contact_number") ||
bq.id?.includes("phone") ||
bq.ui_config?.input_type === "phone" ||
Boolean(bq.title?.toLowerCase().includes("contact number")) ||
Boolean(bq.title?.toLowerCase().includes("phone")) ||
Boolean(bq.title?.includes("شماره تماس")) ||
Boolean(bq.title?.includes("شماره تلفن")))
) {
questionType = "phone";
}
return { return {
id: bq.id, id: bq.id,
title: bq.title || "Untitled", title: bq.title || "Untitled",
type: bq.type,
type: questionType,
order: bq.order !== undefined ? bq.order : index, order: bq.order !== undefined ? bq.order : index,
required: bq.is_required !== undefined ? bq.is_required : bq.required, required: bq.is_required !== undefined ? bq.is_required : bq.required,
baseRequired: bq.required, baseRequired: bq.required,

Loading…
Cancel
Save