|
|
|
@ -1,13 +1,5 @@ |
|
|
|
import { |
|
|
|
isQuestionRequiredForProfile, |
|
|
|
isQuestionVisibleForProfile, |
|
|
|
type QuestionListItem, |
|
|
|
} from "@/data/question-data"; |
|
|
|
import type { |
|
|
|
MarriageFieldValue, |
|
|
|
MarriageGender, |
|
|
|
} from "@/hooks/marriage/types"; |
|
|
|
import { hasQuestionAnswerValue } from "./question-answer-storage"; |
|
|
|
import { type QuestionListItem } from "@/data/question-data"; |
|
|
|
import type { MarriageGender } from "@/hooks/marriage/types"; |
|
|
|
|
|
|
|
export function getStoredAge(): number | null { |
|
|
|
try { |
|
|
|
@ -53,472 +45,10 @@ export function getStoredAge(): number | null { |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
function isFieldAnswered( |
|
|
|
q: Record<string, unknown>, |
|
|
|
field: Record<string, unknown> | undefined, |
|
|
|
): boolean { |
|
|
|
if (!field || !hasQuestionAnswerValue(field.value as MarriageFieldValue)) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
if (q.type === "birthplace") { |
|
|
|
const strVal = String(field.value); |
|
|
|
const parts = strVal.split(",").map((p) => p.trim()); |
|
|
|
return parts.length >= 2 && parts[0].length > 0 && parts[1].length > 0; |
|
|
|
} |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
function hashString(value: string) { |
|
|
|
let hash = 0; |
|
|
|
for (let index = 0; index < value.length; index += 1) { |
|
|
|
hash = (hash * 31 + value.charCodeAt(index)) >>> 0; |
|
|
|
} |
|
|
|
return hash.toString(36); |
|
|
|
} |
|
|
|
|
|
|
|
function slugifyTitle(title: string) { |
|
|
|
const slug = title |
|
|
|
.normalize("NFKD") |
|
|
|
.replace(/[\u0300-\u036f]/g, "") |
|
|
|
.toLowerCase() |
|
|
|
.replace(/[^a-z0-9]+/g, "_") |
|
|
|
.replace(/^_+|_+$/g, ""); |
|
|
|
return slug || `field_${hashString(title)}`; |
|
|
|
} |
|
|
|
|
|
|
|
export function getLocalSectionProgress( |
|
|
|
item: QuestionListItem, |
|
|
|
profile: { gender?: MarriageGender | null } | null | undefined, |
|
|
|
age: number | null, |
|
|
|
): number | null { |
|
|
|
try { |
|
|
|
if (typeof window === "undefined") return null; |
|
|
|
|
|
|
|
if (item.slug === "personality_test" || item.slug === "glasser_5_needs_test") { |
|
|
|
const draftKey = `marriage:tests:${item.slug}:draft`; |
|
|
|
const draftRaw = window.localStorage.getItem(draftKey); |
|
|
|
if (draftRaw) { |
|
|
|
try { |
|
|
|
const parsed = JSON.parse(draftRaw); |
|
|
|
if (parsed && typeof parsed.answers === "object" && parsed.answers !== null) { |
|
|
|
const answeredCount = Object.keys(parsed.answers).length; |
|
|
|
const totalCount = item.slug === "personality_test" ? 187 : 35; |
|
|
|
return Math.max( |
|
|
|
0, |
|
|
|
Math.min( |
|
|
|
100, |
|
|
|
Math.round((answeredCount / totalCount) * 100), |
|
|
|
), |
|
|
|
); |
|
|
|
} |
|
|
|
} catch {} |
|
|
|
} |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
const fields: Record<string, unknown>[] = []; |
|
|
|
let hasFoundStorage = false; |
|
|
|
|
|
|
|
const mainKey = `marriage:sections:${item.slug}:answers`; |
|
|
|
const mainRaw = window.localStorage.getItem(mainKey); |
|
|
|
if (mainRaw) { |
|
|
|
try { |
|
|
|
const parsed = JSON.parse(mainRaw); |
|
|
|
if ( |
|
|
|
parsed && |
|
|
|
Array.isArray(parsed.fields) && |
|
|
|
parsed.fields.length > 0 |
|
|
|
) { |
|
|
|
fields.push(...parsed.fields); |
|
|
|
hasFoundStorage = true; |
|
|
|
} |
|
|
|
} catch {} |
|
|
|
} |
|
|
|
|
|
|
|
if (item.slug === "family_marital_history") { |
|
|
|
const fbRaw = window.localStorage.getItem( |
|
|
|
"marriage:sections:family_background:answers", |
|
|
|
); |
|
|
|
if (fbRaw) { |
|
|
|
try { |
|
|
|
const parsed = JSON.parse(fbRaw); |
|
|
|
if (parsed && Array.isArray(parsed.fields)) { |
|
|
|
fields.push(...parsed.fields); |
|
|
|
hasFoundStorage = true; |
|
|
|
} |
|
|
|
} catch {} |
|
|
|
} |
|
|
|
|
|
|
|
const mhRaw = window.localStorage.getItem( |
|
|
|
"marriage:sections:marital_history_children:answers", |
|
|
|
); |
|
|
|
if (mhRaw) { |
|
|
|
try { |
|
|
|
const parsed = JSON.parse(mhRaw); |
|
|
|
if (parsed && Array.isArray(parsed.fields)) { |
|
|
|
fields.push(...parsed.fields); |
|
|
|
hasFoundStorage = true; |
|
|
|
} |
|
|
|
} catch {} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (!hasFoundStorage || fields.length === 0) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
const profileContext = { |
|
|
|
age, |
|
|
|
gender: profile?.gender, |
|
|
|
}; |
|
|
|
|
|
|
|
const hasDobQuestion = item.questions.some( |
|
|
|
(q) => q.title === "Date of Birth" || q.title === "تاریخ تولد", |
|
|
|
); |
|
|
|
|
|
|
|
const profileVisible = item.questions |
|
|
|
.filter((question) => { |
|
|
|
if ( |
|
|
|
hasDobQuestion && |
|
|
|
(question.title === "Age" || question.title === "سن") |
|
|
|
) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
return isQuestionVisibleForProfile(question, profileContext); |
|
|
|
}) |
|
|
|
.map((question) => ({ |
|
|
|
...question, |
|
|
|
required: isQuestionRequiredForProfile(question, profileContext), |
|
|
|
})); |
|
|
|
|
|
|
|
const getQuestionFieldKey = (question: any, questionIndex: number) => { |
|
|
|
const index = |
|
|
|
question.originalIndex !== undefined |
|
|
|
? question.originalIndex |
|
|
|
: questionIndex; |
|
|
|
return `q${index + 1}_${slugifyTitle(question.englishTitle || question.title)}`; |
|
|
|
}; |
|
|
|
|
|
|
|
const findAnswer = (question: any, questionIndex: number) => { |
|
|
|
const key = getQuestionFieldKey(question, questionIndex); |
|
|
|
const engSlug = slugifyTitle(question.englishTitle || question.title); |
|
|
|
const field = fields.find( |
|
|
|
(f) => |
|
|
|
f && |
|
|
|
(f.key === key || |
|
|
|
f.label === question.title || |
|
|
|
f.label === question.englishTitle || |
|
|
|
(typeof f.key === "string" && |
|
|
|
(f.key.endsWith(`.${engSlug}`) || f.key.endsWith(`_${engSlug}`)))), |
|
|
|
); |
|
|
|
return field?.value; |
|
|
|
}; |
|
|
|
|
|
|
|
// Find employment status question by title or by choices length (10)
|
|
|
|
const employmentQuestionIndex = profileVisible.findIndex((q) => { |
|
|
|
return ( |
|
|
|
q.title === "Employment Status" || |
|
|
|
q.title === "وضعیت اشتغال" || |
|
|
|
(q.type === "dropdown" && q.extras?.options?.length === 10) |
|
|
|
); |
|
|
|
}); |
|
|
|
|
|
|
|
let employmentSelectedOptionIndex = -1; |
|
|
|
if (employmentQuestionIndex !== -1) { |
|
|
|
const employmentQuestion = profileVisible[employmentQuestionIndex]; |
|
|
|
const ans = findAnswer(employmentQuestion, employmentQuestionIndex); |
|
|
|
if (ans) { |
|
|
|
employmentSelectedOptionIndex = |
|
|
|
employmentQuestion.extras?.options?.indexOf(String(ans)) ?? -1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Find Parents' Survival Status question index and check selected index
|
|
|
|
const survivalStatusQuestionIndex = profileVisible.findIndex((q) => { |
|
|
|
return ( |
|
|
|
q.title === "Parents' Survival Status" || |
|
|
|
q.title === "وضعیت حیات والدین" |
|
|
|
); |
|
|
|
}); |
|
|
|
|
|
|
|
let survivalSelectedOptionIndex = -1; |
|
|
|
if (survivalStatusQuestionIndex !== -1) { |
|
|
|
const survivalQuestion = profileVisible[survivalStatusQuestionIndex]; |
|
|
|
const ans = findAnswer(survivalQuestion, survivalStatusQuestionIndex); |
|
|
|
if (ans) { |
|
|
|
survivalSelectedOptionIndex = |
|
|
|
survivalQuestion.extras?.options?.indexOf(String(ans)) ?? -1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Find Parents' Marital Status question index and check selected index
|
|
|
|
const maritalStatusQuestionIndex = profileVisible.findIndex((q) => { |
|
|
|
return ( |
|
|
|
q.title === "Parents' Marital Status" || q.title === "وضعیت تأهل والدین" |
|
|
|
); |
|
|
|
}); |
|
|
|
|
|
|
|
let isCircumstancesSelected = false; |
|
|
|
if (maritalStatusQuestionIndex !== -1) { |
|
|
|
const maritalQuestion = profileVisible[maritalStatusQuestionIndex]; |
|
|
|
const ans = findAnswer(maritalQuestion, maritalStatusQuestionIndex); |
|
|
|
if (ans) { |
|
|
|
const selectedIdx = |
|
|
|
maritalQuestion.extras?.options?.indexOf(String(ans)) ?? -1; |
|
|
|
isCircumstancesSelected = selectedIdx === 2; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Find Current Marital Status question index and check selected index
|
|
|
|
const currentMaritalQuestionIndex = profileVisible.findIndex((q) => { |
|
|
|
return ( |
|
|
|
q.title === "Current Marital Status" || q.title === "وضعیت تأهل فعلی" |
|
|
|
); |
|
|
|
}); |
|
|
|
|
|
|
|
let maritalSelectedIndex = -1; |
|
|
|
if (currentMaritalQuestionIndex !== -1) { |
|
|
|
const maritalQuestion = profileVisible[currentMaritalQuestionIndex]; |
|
|
|
const ans = findAnswer(maritalQuestion, currentMaritalQuestionIndex); |
|
|
|
if (ans) { |
|
|
|
maritalSelectedIndex = |
|
|
|
maritalQuestion.extras?.options?.indexOf(String(ans)) ?? -1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Find Children and Guardianship Status question index
|
|
|
|
const custodyQuestionIndex = profileVisible.findIndex((q) => { |
|
|
|
return ( |
|
|
|
q.title === "Children and Guardianship Status" || |
|
|
|
q.title === "وضعیت فرزند و تکفل" |
|
|
|
); |
|
|
|
}); |
|
|
|
|
|
|
|
let hasChildrenSelected = false; |
|
|
|
let hasAnyGuardianshipSelected = false; |
|
|
|
|
|
|
|
if (custodyQuestionIndex !== -1) { |
|
|
|
const custodyQuestion = profileVisible[custodyQuestionIndex]; |
|
|
|
const ans = findAnswer(custodyQuestion, custodyQuestionIndex); |
|
|
|
if (ans) { |
|
|
|
const ansList = Array.isArray(ans) ? ans.map(String) : [String(ans)]; |
|
|
|
hasChildrenSelected = ansList.some( |
|
|
|
(val) => val.includes("Have children") || val.includes("فرزند دارم"), |
|
|
|
); |
|
|
|
hasAnyGuardianshipSelected = ansList.some( |
|
|
|
(val) => |
|
|
|
val.includes("Have children") || |
|
|
|
val.includes("فرزند دارم") || |
|
|
|
val.includes("under my guardianship") || |
|
|
|
val.includes("تحت تکفل"), |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const filtered = profileVisible.filter((question, index) => { |
|
|
|
// Check if this is one of the marital status/children questions
|
|
|
|
if (currentMaritalQuestionIndex !== -1) { |
|
|
|
const isDuration = |
|
|
|
index === currentMaritalQuestionIndex + 1 || |
|
|
|
question.title === "Previous Marriage Duration" || |
|
|
|
question.title === "مدت ازدواج یا عقد قبلی"; |
|
|
|
|
|
|
|
const isSeparation = |
|
|
|
index === currentMaritalQuestionIndex + 2 || |
|
|
|
question.title === "Reason for Separation" || |
|
|
|
question.title === "علت جدایی، در صورت وجود"; |
|
|
|
|
|
|
|
const isCustody = |
|
|
|
index === currentMaritalQuestionIndex + 3 || |
|
|
|
question.title === "Children and Guardianship Status" || |
|
|
|
question.title === "وضعیت فرزند و تکفل"; |
|
|
|
|
|
|
|
const isChildrenCount = |
|
|
|
index === currentMaritalQuestionIndex + 4 || |
|
|
|
question.title === "Number of Children" || |
|
|
|
question.title === "تعداد فرزندان"; |
|
|
|
|
|
|
|
const isChildrenExplanation = |
|
|
|
index === currentMaritalQuestionIndex + 5 || |
|
|
|
question.title === "Short Children/Guardianship Explanation" || |
|
|
|
question.title === "توضیح کوتاه درباره شرایط فرزند یا تکفل"; |
|
|
|
|
|
|
|
if (isDuration) { |
|
|
|
return [1, 2, 3].includes(maritalSelectedIndex); |
|
|
|
} |
|
|
|
if (isSeparation) { |
|
|
|
return [1, 2].includes(maritalSelectedIndex); |
|
|
|
} |
|
|
|
if (isCustody) { |
|
|
|
return [2, 3].includes(maritalSelectedIndex); |
|
|
|
} |
|
|
|
if (isChildrenCount) { |
|
|
|
return [2, 3].includes(maritalSelectedIndex) && hasChildrenSelected; |
|
|
|
} |
|
|
|
if (isChildrenExplanation) { |
|
|
|
return ( |
|
|
|
[2, 3].includes(maritalSelectedIndex) && hasAnyGuardianshipSelected |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Check if this is one of the three job-related questions
|
|
|
|
if (employmentQuestionIndex !== -1) { |
|
|
|
const isJobTitle = |
|
|
|
index === employmentQuestionIndex + 1 || |
|
|
|
question.title === "Job Title" || |
|
|
|
question.title === "عنوان شغلی"; |
|
|
|
|
|
|
|
const isWorkLocation = |
|
|
|
index === employmentQuestionIndex + 2 || |
|
|
|
question.title === "Work Location" || |
|
|
|
question.title === "محل فعالیت"; |
|
|
|
|
|
|
|
const isMonthlyIncome = |
|
|
|
index === employmentQuestionIndex + 3 || |
|
|
|
question.title === "Monthly Income" || |
|
|
|
question.title === "میزان درآمد ماهانه"; |
|
|
|
|
|
|
|
if (isJobTitle || isWorkLocation || isMonthlyIncome) { |
|
|
|
// If no employment status is selected yet, hide them by default
|
|
|
|
if (employmentSelectedOptionIndex === -1) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// Options logic:
|
|
|
|
// Show all 3 for: index 0 (Full-time), 1 (Part-time), 2 (Self-employed), 3 (Entrepreneur), 5 (Working Student)
|
|
|
|
const showAll = [0, 1, 2, 3, 5].includes( |
|
|
|
employmentSelectedOptionIndex, |
|
|
|
); |
|
|
|
// Hide all 3 for: index 4 (Student), 6 (Student & Job Seeking), 7 (Job Seeking / Unemployed), 8 (Homemaker)
|
|
|
|
const hideAll = [4, 6, 7, 8].includes(employmentSelectedOptionIndex); |
|
|
|
// Special Retired logic: index 9 (Retired)
|
|
|
|
const isRetired = employmentSelectedOptionIndex === 9; |
|
|
|
|
|
|
|
if (showAll) { |
|
|
|
return true; |
|
|
|
} |
|
|
|
if (hideAll) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
if (isRetired) { |
|
|
|
if (isJobTitle || isMonthlyIncome) { |
|
|
|
return true; |
|
|
|
} |
|
|
|
if (isWorkLocation) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Check Parents' Survival Status to decide if Parents' Marital Status is visible
|
|
|
|
if (survivalStatusQuestionIndex !== -1) { |
|
|
|
const isParentsMaritalStatus = |
|
|
|
question.title === "Parents' Marital Status" || |
|
|
|
question.title === "وضعیت تأهل والدین"; |
|
|
|
|
|
|
|
if (isParentsMaritalStatus) { |
|
|
|
return survivalSelectedOptionIndex === 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Default dependsOn logic
|
|
|
|
if (question.logic?.dependsOn) { |
|
|
|
const { title, values } = question.logic.dependsOn; |
|
|
|
const dependentQuestionIndex = profileVisible.findIndex( |
|
|
|
(q) => q.title === title, |
|
|
|
); |
|
|
|
|
|
|
|
if (dependentQuestionIndex !== -1) { |
|
|
|
const dependentQuestion = profileVisible[dependentQuestionIndex]; |
|
|
|
const answer = findAnswer(dependentQuestion, dependentQuestionIndex); |
|
|
|
if (Array.isArray(answer)) { |
|
|
|
return answer.some((ans) => values.includes(String(ans))); |
|
|
|
} |
|
|
|
return values.includes(String(answer)); |
|
|
|
} |
|
|
|
return false; |
|
|
|
} |
|
|
|
return true; |
|
|
|
}); |
|
|
|
|
|
|
|
const activeQuestions = filtered.map((question) => { |
|
|
|
if ( |
|
|
|
question.title === "Short Family Description" || |
|
|
|
question.title === "توضیح کوتاه درباره خانواده" |
|
|
|
) { |
|
|
|
return { |
|
|
|
...question, |
|
|
|
required: isCircumstancesSelected, |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
if ( |
|
|
|
question.title === "Previous Marriage Duration" || |
|
|
|
question.title === "مدت ازدواج یا عقد قبلی" || |
|
|
|
question.title === "Number of Children" || |
|
|
|
question.title === "تعداد فرزندان" || |
|
|
|
question.title === "Short Children/Guardianship Explanation" || |
|
|
|
question.title === "توضیح کوتاه درباره شرایط فرزند یا تکفل" || |
|
|
|
question.title === "Additional details about family responsibility" || |
|
|
|
question.title === "توضیحات تکمیلی درباره مسئولیت خانوادگی" || |
|
|
|
question.title === "Do the supported individual(s) live with you?" || |
|
|
|
question.title === "آیا فرد یا افراد تحت حمایت با شما زندگی میکنند؟" || |
|
|
|
question.title === "What is the custody status of your child(ren)?" || |
|
|
|
question.title === "وضعیت حضانت فرزند یا فرزندان شما چگونه است؟" || |
|
|
|
question.title === |
|
|
|
"Does the custody, visitation, or relocation schedule impact your residence or immigration?" || |
|
|
|
question.title === |
|
|
|
"آیا برنامه حضانت، ملاقات یا جابهجایی فرزند بر محل زندگی یا امکان مهاجرت شما تأثیر میگذارد؟" || |
|
|
|
question.title === |
|
|
|
"What is the payment or receipt status of child support?" || |
|
|
|
question.title === |
|
|
|
"وضعیت پرداخت یا دریافت نفقه و حمایت مالی فرزند چگونه است؟" || |
|
|
|
question.title === |
|
|
|
"Acceptance of necessary communication between future spouse and the other parent" || |
|
|
|
question.title === "پذیرش ارتباط ضروری همسر آینده با والد دیگرِ فرزند" || |
|
|
|
question.title === "پذیرش ارتباط ضروری همسر آینده با والد دیگر فرزند" |
|
|
|
) { |
|
|
|
return { |
|
|
|
...question, |
|
|
|
required: true, |
|
|
|
}; |
|
|
|
} |
|
|
|
return question; |
|
|
|
}); |
|
|
|
|
|
|
|
const requiredQuestions = activeQuestions.filter((q) => q.required); |
|
|
|
|
|
|
|
if (requiredQuestions.length === 0) { |
|
|
|
return 100; |
|
|
|
} |
|
|
|
|
|
|
|
const answeredCount = requiredQuestions.filter((q) => { |
|
|
|
const idx = profileVisible.indexOf(q); |
|
|
|
const key = getQuestionFieldKey(q, idx); |
|
|
|
const engSlug = slugifyTitle(q.englishTitle || q.title); |
|
|
|
const field = fields.find( |
|
|
|
(f) => |
|
|
|
f && |
|
|
|
(f.key === key || |
|
|
|
f.label === q.title || |
|
|
|
f.label === q.englishTitle || |
|
|
|
(typeof f.key === "string" && |
|
|
|
(f.key.endsWith(`.${engSlug}`) || f.key.endsWith(`_${engSlug}`)))), |
|
|
|
); |
|
|
|
return isFieldAnswered(q, field); |
|
|
|
}).length; |
|
|
|
|
|
|
|
return Math.max( |
|
|
|
0, |
|
|
|
Math.min( |
|
|
|
100, |
|
|
|
Math.round((answeredCount / requiredQuestions.length) * 100), |
|
|
|
), |
|
|
|
); |
|
|
|
} catch (_e) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
return null; |
|
|
|
} |