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.
168 lines
5.5 KiB
168 lines
5.5 KiB
"use client";
|
|
|
|
import { useMemo } from "react";
|
|
import { IoAlert, IoCheckmark } from "react-icons/io5";
|
|
import { getLocalSectionProgress, getStoredAge } from "./progress-helper";
|
|
import {
|
|
getQuestionListItems,
|
|
isQuestionListItemVisibleForProfile,
|
|
type QuestionListItem,
|
|
} from "@/data/question-data";
|
|
import { toFrontendSlug } from "@/data/section-slug-map";
|
|
import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main";
|
|
import { useMarriageSectionsQuery } from "@/hooks/marriage/use-sections";
|
|
import { useI18n } from "@/translations/provider";
|
|
|
|
type RequiredStepsCardProps = {
|
|
items?: QuestionListItem[];
|
|
progressBySlug?: Map<string, number>;
|
|
};
|
|
|
|
type RequiredStep = {
|
|
slug: string;
|
|
required: boolean;
|
|
progress: number;
|
|
};
|
|
|
|
function getRequiredStepStats(steps: RequiredStep[]) {
|
|
const requiredSteps = steps.filter((step) => step.required);
|
|
const completedSteps = requiredSteps.filter((step) => step.progress >= 100);
|
|
|
|
return {
|
|
completed: completedSteps.length,
|
|
total: requiredSteps.length,
|
|
};
|
|
}
|
|
|
|
export default function RequiredStepsCard({
|
|
items,
|
|
progressBySlug,
|
|
}: RequiredStepsCardProps = {}) {
|
|
const { dictionary: t, locale } = useI18n();
|
|
const { data: profile } = useMarriageProfileQuery();
|
|
const { data: sections } = useMarriageSectionsQuery();
|
|
|
|
const questionListItems = useMemo(
|
|
() =>
|
|
items ??
|
|
getQuestionListItems(locale).filter((item) =>
|
|
isQuestionListItemVisibleForProfile(item, {
|
|
gender: profile?.gender,
|
|
}),
|
|
),
|
|
[items, locale, profile?.gender],
|
|
);
|
|
|
|
const steps: RequiredStep[] = useMemo(() => {
|
|
const age = getStoredAge();
|
|
type SectionType = NonNullable<typeof sections>[number];
|
|
const sectionMap = new Map<string, SectionType>();
|
|
sections?.forEach((s) => {
|
|
sectionMap.set(toFrontendSlug(s.slug), s);
|
|
sectionMap.set(s.slug, s);
|
|
});
|
|
|
|
return questionListItems.map((item) => {
|
|
const localProgress = getLocalSectionProgress(item, profile, age);
|
|
const section = sectionMap.get(item.slug);
|
|
|
|
let progress = 0;
|
|
if (localProgress !== null) {
|
|
progress = localProgress;
|
|
} else if (
|
|
progressBySlug &&
|
|
typeof progressBySlug.get(item.slug) === "number"
|
|
) {
|
|
progress = progressBySlug.get(item.slug) ?? 0;
|
|
} else if (item.slug === "family_marital_history" && sections) {
|
|
const fbSec = sections.find((s) => s.slug === "family_background");
|
|
const mhSec = sections.find((s) => s.slug === "marital_history");
|
|
if (fbSec || mhSec) {
|
|
const fbTotal = fbSec?.total_steps ?? 6;
|
|
const fbCurrent = fbSec
|
|
? Math.round((fbSec.completion_percent / 100) * fbTotal)
|
|
: 0;
|
|
const mhTotal = mhSec?.total_steps ?? 6;
|
|
const mhCurrent = mhSec
|
|
? Math.round((mhSec.completion_percent / 100) * mhTotal)
|
|
: 0;
|
|
progress =
|
|
fbTotal + mhTotal > 0
|
|
? Math.max(
|
|
0,
|
|
Math.min(
|
|
100,
|
|
Math.round(
|
|
((fbCurrent + mhCurrent) / (fbTotal + mhTotal)) * 100,
|
|
),
|
|
),
|
|
)
|
|
: 0;
|
|
}
|
|
} else if (section) {
|
|
progress = Math.max(
|
|
0,
|
|
Math.min(100, Math.round(section.completion_percent)),
|
|
);
|
|
} else {
|
|
progress = item.progress;
|
|
}
|
|
|
|
return {
|
|
slug: item.slug,
|
|
required: Boolean(item.required),
|
|
progress,
|
|
};
|
|
});
|
|
}, [questionListItems, progressBySlug, sections, profile]);
|
|
|
|
const { completed, total } = getRequiredStepStats(steps);
|
|
const completion = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
const isCompleted = total > 0 && completed === total;
|
|
|
|
return (
|
|
<section
|
|
aria-label="Required steps progress"
|
|
className="rounded-[15px] bg-[#40506A] p-4 text-white shadow-[0_18px_34px_rgba(38,52,73,0.16)]"
|
|
>
|
|
<div className="flex items-center justify-between gap-5">
|
|
<div className="min-w-0 flex-1">
|
|
<div className="flex items-center gap-3">
|
|
<span className="flex h-[24px] w-[24px] shrink-0 items-center justify-center rounded-full bg-white text-[#40506A]">
|
|
{isCompleted ? (
|
|
<IoCheckmark aria-hidden="true" className="text-[16px]" />
|
|
) : (
|
|
<IoAlert aria-hidden="true" className="text-[16px]" />
|
|
)}
|
|
</span>
|
|
<h2 className="group-16 leading-none font-bold tracking-[-0.02em]">
|
|
{t.questions.requiredSteps}
|
|
</h2>
|
|
</div>
|
|
|
|
<p className="mt-2.5 max-w-[220px] group-12 leading-[1.25] font-medium text-white/80">
|
|
{isCompleted
|
|
? t.questions.requiredStepsDescriptionCompleted
|
|
: t.questions.requiredStepsDescription}
|
|
</p>
|
|
</div>
|
|
|
|
<div
|
|
role="img"
|
|
aria-label={t.questions.requiredStepsProgress
|
|
.replace("{completed}", String(completed))
|
|
.replace("{total}", String(total))}
|
|
className="relative flex h-[60px] w-[60px] shrink-0 items-center justify-center rounded-full"
|
|
style={{
|
|
background: `conic-gradient(#FFFFFF ${completion}%,rgba(255,255,255,0.24) 0)`,
|
|
}}
|
|
>
|
|
<div className="absolute inset-[6px] rounded-full bg-[#40506A]" />
|
|
<span className="relative group-14 leading-none font-bold tracking-[-0.02em]">
|
|
{completed}/{total}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|