"use client"; import { type ReactNode, useCallback, useEffect, useRef, useState, } from "react"; type QuestionProgressTrackerProps = { children: ReactNode; total: number; }; function isQuestionAnswered(question: Element) { const explicitAnsweredState = question.getAttribute("data-question-answered"); if (explicitAnsweredState === "true") { return true; } if (explicitAnsweredState === "false") { return false; } const inputs = Array.from( question.querySelectorAll< HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement >("input, select, textarea"), ); return inputs.some((input) => { if (input instanceof HTMLInputElement) { if (input.type === "checkbox" || input.type === "radio") { return input.checked; } if (input.type === "file") { return input.files !== null && input.files.length > 0; } } return input.value.trim().length > 0; }); } export function QuestionProgressTracker({ children, total: initialTotal, }: QuestionProgressTrackerProps) { const containerRef = useRef(null); const [answered, setAnswered] = useState(0); const [total, setTotal] = useState(initialTotal); const safeTotal = Math.max(total, 0); const progress = safeTotal > 0 ? (answered / safeTotal) * 100 : 0; const updateProgress = useCallback(() => { const container = containerRef.current; if (!container) { return; } const requiredQuestions = Array.from( container.querySelectorAll("[data-question-required='true']"), ).filter((el) => el.getAttribute("data-question-disabled") !== "true"); const nextTotal = requiredQuestions.length; const nextAnswered = requiredQuestions.filter(isQuestionAnswered).length; setTotal(nextTotal); setAnswered(nextAnswered); }, []); useEffect(() => { const container = containerRef.current; if (!container) { return; } const observer = new MutationObserver(updateProgress); observer.observe(container, { attributeFilter: ["data-question-answered"], attributes: true, subtree: true, }); updateProgress(); return () => observer.disconnect(); }, [updateProgress]); return (