"use client"; import { useState, useEffect, useRef } from "react"; import type { Module, Section } from "@/lib/learn-data"; import Quiz from "./Quiz"; import { markSectionRead } from "./ProgressTracker"; interface LearnModuleProps { module: Module; defaultOpen?: boolean; } const SECTIONS_KEY = (moduleId: string) => `arcisvlm_sections_${moduleId}`; function loadReadSections(moduleId: string): Set { if (typeof window === "undefined") return new Set(); try { const raw = localStorage.getItem(SECTIONS_KEY(moduleId)); if (raw) return new Set(JSON.parse(raw)); } catch { /* ignore */ } return new Set(); } export default function LearnModule({ module: mod, defaultOpen = false }: LearnModuleProps) { const [open, setOpen] = useState(defaultOpen); const [readSections, setReadSections] = useState>(new Set()); const [quizCorrect, setQuizCorrect] = useState(0); useEffect(() => { setReadSections(loadReadSections(mod.id)); }, [mod.id]); function handleSectionRead(sectionId: string) { markSectionRead(mod.id, sectionId); setReadSections((prev) => new Set([...prev, sectionId])); } const readCount = readSections.size; const totalSections = mod.sections.length; const pct = Math.round((readCount / totalSections) * 100); const quizPassed = quizCorrect === mod.quiz.length && mod.quiz.length > 0; return (
{/* Module header */} {/* Content */} {open && (
{/* Sections */}
{mod.sections.map((section) => ( handleSectionRead(section.id)} /> ))}
{/* Quiz */}

Module Quiz

setQuizCorrect(correct)} />
)}
); } // ────────────────────────────────────────────────────────────── // Section Card — collapsible content with analogy + code pointer // ────────────────────────────────────────────────────────────── interface SectionCardProps { section: Section; isRead: boolean; onRead: () => void; } function SectionCard({ section, isRead, onRead }: SectionCardProps) { const [open, setOpen] = useState(false); const ref = useRef(null); function handleToggle() { const opening = !open; setOpen(opening); if (opening && !isRead) { // Mark as read when first opened onRead(); } } return (
{open && (
{/* Electronics analogy callout */} {section.analogy && (

Electronics Analogy

{section.analogy}

)} {/* Main content */} {/* Code pointer */} {section.codePointer && (
{section.codePointer.label} {section.codePointer.file} {section.codePointer.line ? `:${section.codePointer.line}` : ""}
)}
)}
); } // ────────────────────────────────────────────────────────────── // Content renderer — parses simple markdown-like content // Supports: **bold**, `inline code`, code blocks, lists, tables // ────────────────────────────────────────────────────────────── function ContentRenderer({ content }: { content: string }) { const lines = content.trim().split("\n"); const elements: React.ReactNode[] = []; let i = 0; while (i < lines.length) { const line = lines[i]; // Skip empty lines (used as separators) if (line.trim() === "") { i++; continue; } // Code block if (line.trim().startsWith("```")) { const blockLines: string[] = []; i++; while (i < lines.length && !lines[i].trim().startsWith("```")) { blockLines.push(lines[i]); i++; } i++; // skip closing ``` elements.push(
          {blockLines.join("\n")}
        
); continue; } // Table (starts with |) if (line.trim().startsWith("|")) { const tableLines: string[] = []; while (i < lines.length && lines[i].trim().startsWith("|")) { tableLines.push(lines[i]); i++; } elements.push(); continue; } // Bullet list if (line.trim().startsWith("- ") || line.trim().startsWith("* ")) { const items: string[] = []; while (i < lines.length && (lines[i].trim().startsWith("- ") || lines[i].trim().startsWith("* "))) { items.push(lines[i].trim().replace(/^[-*] /, "")); i++; } elements.push(
    {items.map((item, j) => (
  • ·
  • ))}
); continue; } // Numbered list if (/^\d+\./.test(line.trim())) { const items: string[] = []; while (i < lines.length && /^\d+\./.test(lines[i].trim())) { items.push(lines[i].trim().replace(/^\d+\.\s*/, "")); i++; } elements.push(
    {items.map((item, j) => (
  1. {j + 1}.
  2. ))}
); continue; } // Regular paragraph elements.push(

); i++; } return
{elements}
; } function InlineText({ text }: { text: string }) { // Parse **bold**, `code`, and plain text segments const parts = text.split(/(\*\*[^*]+\*\*|`[^`]+`)/g); return ( <> {parts.map((part, i) => { if (part.startsWith("**") && part.endsWith("**")) { return {part.slice(2, -2)}; } if (part.startsWith("`") && part.endsWith("`")) { return {part.slice(1, -1)}; } return {part}; })} ); } function TableRenderer({ lines }: { lines: string[] }) { // Parse markdown table const rows = lines .filter((l) => !l.trim().match(/^[|\s-]+$/)) // skip separator rows .map((l) => l .trim() .replace(/^\|/, "") .replace(/\|$/, "") .split("|") .map((cell) => cell.trim()) ); if (rows.length === 0) return null; const [header, ...body] = rows; return (
{header.map((h, i) => ( ))} {body.map((row, ri) => ( {row.map((cell, ci) => ( ))} ))}
{h}
); }