import { createPortal } from 'react-dom'; import { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { getSikshyaApi, SIKSHYA_ENDPOINTS } from '../../api'; type CourseRow = { id: number; title: string; status?: string }; type CoursePrereqResp = { ok?: boolean; prerequisite_courses?: CourseRow[]; }; type LockedLessonRow = { lesson_id: number; title: string; status?: string; prerequisite_lessons?: CourseRow[]; }; type LessonSummaryResp = { ok?: boolean; locked_lessons?: LockedLessonRow[]; }; /** * Clickable count in the Prerequisites table; opens a popover with the * underlying required courses (enrollment) or lessons + their required lessons. */ export function PrerequisiteLockDetailPopover(props: { kind: 'enrollment' | 'lessons'; courseId: number; courseTitle: string; count: number; enabled: boolean; }) { const { kind, courseId, courseTitle, count, enabled } = props; const [open, setOpen] = useState(false); const wrapRef = useRef(null); const panelRef = useRef(null); const [pos, setPos] = useState<{ top: number; left: number; maxW: number } | null>(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [courses, setCourses] = useState([]); const [lockedLessons, setLockedLessons] = useState([]); useLayoutEffect(() => { if (!open || !wrapRef.current) { setPos(null); return; } const update = () => { const el = wrapRef.current; if (!el) return; const rect = el.getBoundingClientRect(); const gap = 6; const pad = 10; const maxW = Math.min(22 * 16, Math.max(240, window.innerWidth - pad * 2)); let left = rect.left; if (left + maxW > window.innerWidth - pad) { left = Math.max(pad, window.innerWidth - pad - maxW); } setPos({ top: rect.bottom + gap, left, maxW }); }; update(); const id = window.requestAnimationFrame(update); window.addEventListener('resize', update); window.addEventListener('scroll', update, true); return () => { window.cancelAnimationFrame(id); window.removeEventListener('resize', update); window.removeEventListener('scroll', update, true); }; }, [open]); useEffect(() => { if (!open) { setError(null); setCourses([]); setLockedLessons([]); setLoading(false); return; } if (!enabled || courseId <= 0) return; if (count <= 0) { setLoading(false); setError(null); setCourses([]); setLockedLessons([]); return; } let cancelled = false; setLoading(true); setError(null); void (async () => { try { if (kind === 'enrollment') { const data = await getSikshyaApi().get(SIKSHYA_ENDPOINTS.pro.coursePrerequisites(courseId)); if (cancelled) return; setCourses(Array.isArray(data.prerequisite_courses) ? data.prerequisite_courses : []); } else { const data = await getSikshyaApi().get( SIKSHYA_ENDPOINTS.pro.courseLessonPrerequisiteSummary(courseId) ); if (cancelled) return; setLockedLessons(Array.isArray(data.locked_lessons) ? data.locked_lessons : []); } } catch (e) { if (!cancelled) setError(e instanceof Error ? e.message : 'Could not load details'); } finally { if (!cancelled) setLoading(false); } })(); return () => { cancelled = true; }; }, [open, enabled, courseId, kind, count]); useEffect(() => { if (!open) return; const onPointer = (e: MouseEvent | TouchEvent) => { const t = e.target; if (!(t instanceof Node)) return; if (wrapRef.current?.contains(t) || panelRef.current?.contains(t)) return; setOpen(false); }; const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') setOpen(false); }; document.addEventListener('mousedown', onPointer); document.addEventListener('touchstart', onPointer, { passive: true }); document.addEventListener('keydown', onKey); return () => { document.removeEventListener('mousedown', onPointer); document.removeEventListener('touchstart', onPointer); document.removeEventListener('keydown', onKey); }; }, [open]); const label = kind === 'enrollment' ? `${count} course${count === 1 ? '' : 's'}` : `${count} lesson${count === 1 ? '' : 's'}`; const title = kind === 'enrollment' ? 'Enrollment lock' : 'Lesson locks'; const panel = open && pos ? (

{title} {courseTitle}

{count <= 0 ? (

{kind === 'enrollment' ? 'No prior courses are required before enrollment.' : 'No lessons in this course use prerequisite locks yet.'}

) : loading ? (

Loading…

) : error ? (

{error}

) : kind === 'enrollment' ? ( courses.length === 0 ? (

No required courses (counts may be syncing).

) : (
    {courses.map((c) => (
  • {c.title || `Course #${c.id}`} {c.status && c.status !== 'publish' ? ( ({c.status}) ) : null} #{c.id}
  • ))}
) ) : lockedLessons.length === 0 ? (

No lesson locks found (counts may be syncing).

) : (
    {lockedLessons.map((row) => (
  • {row.title || `Lesson #${row.lesson_id}`} #{row.lesson_id}

    Requires completing first:

      {(row.prerequisite_lessons ?? []).map((p) => (
    • · {p.title || `Lesson #${p.id}`} #{p.id}
    • ))}
  • ))}
)}
) : null; return ( <> {typeof document !== 'undefined' && panel ? createPortal(panel, document.body) : null} ); }