({ roadmap, questions }: Props)
| 60 | } |
| 61 | |
| 62 | export default function RoadmapView({ roadmap, questions }: Props) { |
| 63 | const { syncNow, syncVersion } = useAuth(); |
| 64 | const slugToQuestion = useMemo(() => { |
| 65 | const map = new Map<string, Question>(); |
| 66 | questions.forEach((q) => map.set(q.slug, q)); |
| 67 | return map; |
| 68 | }, [questions]); |
| 69 | |
| 70 | const [completed, setCompleted] = useState<Set<number>>(new Set()); |
| 71 | const [starred, setStarred] = useState<Set<number>>(new Set()); |
| 72 | const [notes, setNotes] = useState<Record<number, string>>({}); |
| 73 | const [, setSolvedDates] = useState<Record<number, string>>({}); |
| 74 | const [, setReminders] = useState<Record<number, Reminder>>({}); |
| 75 | const [collapsedPhases, setCollapsedPhases] = useState<Set<string | number>>( |
| 76 | new Set() |
| 77 | ); |
| 78 | const [expandedHints, setExpandedHints] = useState<Set<string>>(new Set()); |
| 79 | const [editingNote, setEditingNote] = useState<{ |
| 80 | id: number; |
| 81 | title: string; |
| 82 | draft: string; |
| 83 | confirmDiscard: boolean; |
| 84 | } | null>(null); |
| 85 | |
| 86 | useEffect(() => { |
| 87 | setCompleted(loadCompleted()); |
| 88 | setStarred(loadStarred()); |
| 89 | setNotes(loadNotes()); |
| 90 | setSolvedDates(loadSolvedDates()); |
| 91 | setReminders(loadReminders()); |
| 92 | }, []); |
| 93 | |
| 94 | // Reload from localStorage when remote sync arrives |
| 95 | useEffect(() => { |
| 96 | if (syncVersion === 0) return; |
| 97 | setCompleted(loadCompleted()); |
| 98 | setStarred(loadStarred()); |
| 99 | setNotes(loadNotes()); |
| 100 | setSolvedDates(loadSolvedDates()); |
| 101 | setReminders(loadReminders()); |
| 102 | }, [syncVersion]); |
| 103 | |
| 104 | const toggleCompleted = useCallback((id: number) => { |
| 105 | let completing = false; |
| 106 | setCompleted((prev) => { |
| 107 | const next = new Set(prev); |
| 108 | completing = !next.has(id); |
| 109 | if (completing) next.add(id); |
| 110 | else next.delete(id); |
| 111 | saveCompleted(next); |
| 112 | trackEvent("question_toggle", { |
| 113 | question_id: id, |
| 114 | completed: completing, |
| 115 | }); |
| 116 | return next; |
| 117 | }); |
| 118 | setSolvedDates((prev) => { |
| 119 | const next = { ...prev }; |
nothing calls this directly
no test coverage detected