import { useEffect, useState, useMemo } from 'react'; import { CheckCircle2, Circle, BookOpen, Users, Search, ChevronLeft, ChevronRight, RefreshCcw, AlertTriangle } from 'lucide-react'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Skeleton } from '@/components/ui/skeleton'; import { Separator } from '@/components/ui/separator'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose, } from '@/components/ui/dialog'; import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationPrevious, PaginationNext, PaginationEllipsis, } from '@/components/ui/pagination'; import { useAdminCourseReadingProgress } from '@/contexts/AdminCourseReadingProgressContext'; import { useDateFormat } from '@/hooks/useDateFormat'; const PAGE_SIZE = 10; // ─── Helpers ────────────────────────────────────────────────────────────────── function StatusBadge({ status }) { if (!status) return Not started; return status === 'completed' ? Completed : In Progress; } function ProgressBar({ value, total, className = '' }) { const pct = total > 0 ? Math.round((value / total) * 100) : 0; return (
{value} / {total}
); } // Only relevant once reading is fully done but the course still isn't 'completed' — // i.e. a still-unpassed unit quiz and/or course assessment is what's blocking it. function PendingNotice({ entry }) { const readingDone = entry.lessons_total > 0 && entry.lessons_completed === entry.lessons_total; if (!readingDone || entry.course_status === 'completed') return null; if (!entry.quizzes_pending && !entry.assessment_pending) return null; const parts = []; if (entry.quizzes_pending > 0) { parts.push(`${entry.quizzes_pending} quiz${entry.quizzes_pending === 1 ? '' : 'zes'} needed`); } if (entry.assessment_pending) parts.push('Assessment needed'); return (

{parts.join(' · ')}

); } function UserAvatar({ name, email, avatarUrl }) { const initials = name ? name.split(' ').map((n) => n[0]).slice(0, 2).join('').toUpperCase() : (email?.[0] ?? '?').toUpperCase(); return ( {initials} ); } // ─── Dialog: full breakdown for one user ───────────────────────────────────── function UserDetailDialog({ open, onOpenChange, entry, courseId }) { const { detailCache, detailLoading, fetchUserReadingProgress } = useAdminCourseReadingProgress(); const { fmtDate, fmtDateShort } = useDateFormat(); const breakdown = entry ? detailCache[entry.user_id] : null; useEffect(() => { if (open && entry && !breakdown) { fetchUserReadingProgress(courseId, entry.user_id); } }, [open, entry]); const lastSeen = entry?.last_accessed_at ? fmtDate(entry.last_accessed_at) : '—'; return (
{entry && }
{entry?.user.full_name ?? No name} {entry?.user.email}
{/* ── Meta strip ── */} {entry && (
Last seen {lastSeen} {entry.lessons_completed} / {entry.lessons_total} lessons
)} {entry && } {/* ── Progress bar ── */} {entry && ( )} {/* ── Unit / lesson breakdown — fills remaining height and scrolls ── */} {detailLoading && !breakdown ? (
{[...Array(4)].map((_, i) => (
))}
) : breakdown?.length ? (
{breakdown.map((unit, ui) => (
{/* Unit header */}
{unit.status === 'completed' ? : unit.status === 'in_progress' ? : } Unit {ui + 1}: {unit.title} {unit.status && }
{/* Lesson rows */}
{unit.lessons.map((lesson) => (
{lesson.status === 'completed' ? : lesson.status === 'in_progress' ? : } {lesson.title} {lesson.status === 'completed' && lesson.completed_at && ( {fmtDateShort(lesson.completed_at)} )}
))}
))}
) : (

No lesson data available.

)}
); } // ─── User summary card ──────────────────────────────────────────────────────── function UserCard({ entry, onOpen }) { const { fmtDate } = useDateFormat(); const lastSeen = entry.last_accessed_at ? fmtDate(entry.last_accessed_at) : '—'; return ( ); } // ─── Pagination controls ────────────────────────────────────────────────────── function PaginationControls({ page, totalPages, onPage }) { if (totalPages <= 1) return null; const pages = []; for (let i = 1; i <= totalPages; i++) pages.push(i); // Show at most 5 page numbers around current const getVisible = () => { if (totalPages <= 5) return pages; if (page <= 3) return [1, 2, 3, 4, null, totalPages]; if (page >= totalPages - 2) return [1, null, totalPages - 3, totalPages - 2, totalPages - 1, totalPages]; return [1, null, page - 1, page, page + 1, null, totalPages]; }; return ( { e.preventDefault(); if (page > 1) onPage(page - 1); }} className={page === 1 ? 'pointer-events-none opacity-50' : ''} /> {getVisible().map((p, i) => p === null ? ( ) : ( { e.preventDefault(); onPage(p); }} > {p} ) )} { e.preventDefault(); if (page < totalPages) onPage(page + 1); }} className={page === totalPages ? 'pointer-events-none opacity-50' : ''} /> ); } // ─── Main component ─────────────────────────────────────────────────────────── export default function CourseReadingProgressList({ courseId }) { const { progressList, listLoading, fetchCourseReadingProgress } = useAdminCourseReadingProgress(); const [searchInput, setSearchInput] = useState(''); const [query, setQuery] = useState(''); const [page, setPage] = useState(1); const [dialogEntry, setDialogEntry] = useState(null); useEffect(() => { fetchCourseReadingProgress(courseId); }, [courseId]); const handleSearchSubmit = (e) => { e.preventDefault(); setQuery(searchInput.trim()); setPage(1); }; // ── Filter ──────────────────────────────────────────────────────────────── const filtered = useMemo(() => { const q = query.trim().toLowerCase(); if (!q) return progressList; return progressList.filter((e) => e.user.full_name?.toLowerCase().includes(q) || e.user.email?.toLowerCase().includes(q) ); }, [progressList, query]); // ── Paginate ────────────────────────────────────────────────────────────── const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE)); const paginated = filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE); const completedCount = progressList.filter((e) => e.course_status === 'completed').length; const inProgressCount = progressList.length - completedCount; // ── Loading skeleton ────────────────────────────────────────────────────── if (listLoading) { return (
{[...Array(4)].map((_, i) => (
))}
); } // ── Empty state ─────────────────────────────────────────────────────────── if (!progressList.length) { return (

No students have started reading this course yet.

); } return (
{/* ── Summary strip ── */}
{progressList.length} enrolled {completedCount} completed {inProgressCount} in progress
{/* ── Search ── */}
setSearchInput(e.target.value.slice(0, 50))} maxLength={50} className="bg-background pl-9 pr-16" /> = 50 ? 'text-destructive' : 'text-muted-foreground'}`}> {searchInput.length}/50
{/* ── List ── */} {filtered.length === 0 ? (

No results for "{query}".

) : (
{paginated.map((entry) => ( ))}
)} {/* ── Pagination ── */} {/* ── Detail Dialog ── */} { if (!v) setDialogEntry(null); }} entry={dialogEntry} courseId={courseId} />
); }