You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

283 lines
7.4 KiB

import type {
FormSchemaResponse,
FormSection,
FormQuestion,
FormOverviewResponse,
} from "@/hooks/marriage/use-form-schema";
import { defaultLocale, type Locale } from "@/translations/config";
export type QuestionCardIcon =
| "profile"
| "contact"
| "health"
| "education"
| "family"
| "marriage"
| "lifestyle"
| "criteria"
| "verification"
| "personality"
| "glasser"
| "details"
| "checklist"
| "family_marital";
export const sectionSlugIconMap: Record<string, QuestionCardIcon> = {
personal_info: "profile",
personal_identity: "profile",
contact_residence_family_communication: "contact",
contact_residence: "contact",
appearance_health_activity: "health",
appearance_health: "health",
education_career_economic_status: "education",
education_career: "education",
family_background: "family",
marital_history_children: "marriage",
marital_history: "marriage",
beliefs_lifestyle_boundaries: "lifestyle",
beliefs_lifestyle: "lifestyle",
future_spouse_criteria: "criteria",
spouse_criteria: "criteria",
identity_verification: "verification",
documents_verification: "verification",
personality_test: "personality",
cattell_test: "personality",
glasser_5_needs_test: "glasser",
glasser_test: "glasser",
};
export function resolveSectionIcon(
slug: string,
backendIcon?: string,
): QuestionCardIcon {
if (slug && sectionSlugIconMap[slug]) {
return sectionSlugIconMap[slug];
}
if (backendIcon && iconMap[backendIcon]) {
return iconMap[backendIcon];
}
return "details";
}
export type QuestionExtras = {
placeHolder: string;
range: [number, number];
options: string[];
noSearch?: boolean;
};
export type QuestionAudienceRule = {
genders?: string[];
maxAge?: number;
minAge?: number;
};
export type QuestionField = {
id: string;
title: string;
type: string;
order: number;
required: boolean;
baseRequired: boolean;
isVisible: boolean;
private?: boolean;
description: string;
tooltip: string;
validation?: any;
ui_config?: any;
extras: QuestionExtras;
audience?: QuestionAudienceRule | any;
requiredWhen?: any;
conditionalRule?: any;
visibility?: any;
logic?: any;
showGuardianNotice?: boolean;
options: {
id: string;
value: string | number;
label: string;
order: number;
}[];
};
export type QuestionListItem = {
slug: string;
title: string;
estimate: string;
progress: number;
icon: QuestionCardIcon;
required: boolean;
showInfoBadge?: boolean;
summary: string;
checkpoints: string[];
tooltip?: string;
note?: string;
questions: QuestionField[];
};
const iconMap: Record<string, QuestionCardIcon> = {
"user-circle": "profile",
user: "profile",
person: "profile",
school: "education",
education: "education",
"heart-handshake": "marriage",
"heart-pulse": "health",
activity: "health",
"file-text": "contact",
contact: "contact",
users: "family",
family: "family",
compass: "lifestyle",
"target-heart": "criteria",
"shield-check": "verification",
"shield": "verification",
brain: "personality",
"layout-grid": "checklist",
"star-half": "glasser",
star: "glasser",
};
export function mapBackendQuestionToFrontend(
bq: FormQuestion,
index: number,
): QuestionField {
const minVal = bq.validation?.min;
const maxVal = bq.validation?.max;
const rangeVal = bq.validation?.range;
const uiRange = bq.ui_config?.range;
const uiMin = bq.ui_config?.min;
const uiMax = bq.ui_config?.max;
let range: [number, number] = [0, 0];
if (
Array.isArray(uiRange) &&
uiRange.length === 2 &&
(uiRange[0] !== 0 || uiRange[1] !== 0)
) {
range = [Number(uiRange[0]), Number(uiRange[1])];
} else if (
Array.isArray(rangeVal) &&
rangeVal.length === 2 &&
(rangeVal[0] !== 0 || rangeVal[1] !== 0)
) {
range = [Number(rangeVal[0]), Number(rangeVal[1])];
} else if (minVal !== undefined && maxVal !== undefined) {
range = [Number(minVal), Number(maxVal)];
} else if (uiMin !== undefined && uiMax !== undefined) {
range = [Number(uiMin), Number(uiMax)];
} else if (Array.isArray(uiRange) && uiRange.length === 2) {
range = [Number(uiRange[0]), Number(uiRange[1])];
}
return {
id: bq.id,
title: bq.title || "Untitled",
type: bq.type,
order: bq.order !== undefined ? bq.order : index,
required: bq.is_required !== undefined ? bq.is_required : bq.required,
baseRequired: bq.required,
isVisible: bq.is_visible,
private: bq.ui_config?.private,
validation: bq.validation,
ui_config: bq.ui_config,
description: bq.description || "",
tooltip: bq.tooltip || "",
extras: {
placeHolder: bq.placeholder || "",
options: bq.options?.map((o) => o.label) || [],
range,
noSearch: bq.ui_config?.noSearch,
},
showGuardianNotice: bq.show_guardian_notice,
audience: bq.audience || undefined,
requiredWhen: bq.required_when || undefined,
conditionalRule:
bq.conditional_rule || bq.visibility || bq.logic || undefined,
visibility: bq.visibility || bq.conditional_rule || undefined,
logic: bq.logic || undefined,
options: [...(bq.options || [])].sort(
(a, b) => (a.order ?? 0) - (b.order ?? 0),
),
};
}
export function mapBackendSectionToFrontend(
section: FormSection,
progress: number,
): QuestionListItem {
const allQuestions: QuestionField[] = [];
let index = 0;
const cards = [...(section.cards || [])].sort(
(a, b) => (a.order ?? 0) - (b.order ?? 0),
);
cards.forEach((card) => {
const questions = [...(card.questions || [])].sort(
(a, b) => (a.order ?? 0) - (b.order ?? 0),
);
questions.forEach((q) => {
const fq = mapBackendQuestionToFrontend(q, index);
allQuestions.push(fq);
index++;
});
});
return {
slug: section.id,
title: section.title,
estimate: section.estimated_minutes
? `${section.estimated_minutes} min`
: "5 min",
progress: progress,
icon: resolveSectionIcon(section.id, section.icon),
required: section.is_required,
showInfoBadge: false,
summary: "",
checkpoints: allQuestions.map((q) => q.title),
tooltip: "",
questions: allQuestions,
};
}
export function convertSchemaToFrontendItems(
schema: FormSchemaResponse | undefined,
locale: Locale = defaultLocale,
): QuestionListItem[] {
if (!schema) return [];
const rawItems = schema.sections.map((sec) => {
const progInfo = schema.progress?.sections_progress?.[sec.id];
const progress = progInfo ? progInfo.completion_percent : 0;
return mapBackendSectionToFrontend(sec, progress);
});
return rawItems.sort(
(a, b) =>
(schema.sections.find((s) => s.id === a.slug)?.order ?? 0) -
(schema.sections.find((s) => s.id === b.slug)?.order ?? 0),
);
}
export function convertOverviewToFrontendItems(
overview: FormOverviewResponse | undefined,
): QuestionListItem[] {
if (!overview) return [];
return [...overview.sections]
.sort((a, b) => a.order - b.order)
.map((section) => ({
slug: section.id,
title: section.title,
estimate: section.estimated_minutes
? `${section.estimated_minutes} min`
: "5 min",
progress: section.progress?.completion_percent ?? 0,
icon: resolveSectionIcon(section.id, section.icon),
required: section.is_required,
showInfoBadge: false,
summary: "",
checkpoints: [],
tooltip: "",
questions: [],
}));
}