import { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import {
ArrowLeft, ClipboardList, NotebookPen,
CheckCircle2, Circle, Users, Activity,
ChevronDown, ChevronUp,
} from "lucide-react";
import { useCourses } from "@/contexts/AdminCoursesContext";
import { PageMeta } from "@/contexts/MetadataContext";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
// ─── Helpers ──────────────────────────────────────────────────────────────────
function InfoRow({ label, children }) {
return (
{label}
{children ?? —}
);
}
function SectionCard({ title, children }) {
return (
{title && (<>
{title}
>)}
{children}
);
}
function StatCard({ label, value, color = "default" }) {
const colors = {
default: "bg-card border",
green: "bg-green-500/5 border-green-500/20",
red: "bg-red-500/5 border-red-500/20",
blue: "bg-blue-500/5 border-blue-500/20",
amber: "bg-amber-500/5 border-amber-500/20",
};
return (
);
}
const TYPE_LABELS = {
multiple_choice: "Multiple Choice",
multi_select: "Multi Select",
true_false: "True / False",
};
function QuestionView({ question, index }) {
return (
{index + 1}
{question.question?.trim() || Untitled}
{TYPE_LABELS[question.type] ?? question.type}
{question.points ?? 1} pt{(question.points ?? 1) !== 1 ? "s" : ""}
{question.type === "multi_select" && (() => {
const cnt = (question.options ?? []).filter((o) => o.is_correct).length;
return cnt > 0 ? (
Students select {cnt} answer{cnt !== 1 ? "s" : ""}
) : null;
})()}
{(question.options ?? []).map((opt, oi) => (
-
{opt.is_correct ? (
) : (
)}
{opt.text}
))}
);
}
// ─── Completions tab ──────────────────────────────────────────────────────────
function CompletionRow({ row }) {
const [open, setOpen] = useState(false);
return (
<>
setOpen((v) => !v)}
>
{row.full_name}
{row.email}
|
{row.attempt_count} |
{row.best_score}%
|
{row.passed ? (
Passed
) : (
Not yet
)}
|
{row.latest_at ? new Date(row.latest_at).toLocaleDateString() : "—"}
|
{open ? : }
|
{open && (
| Attempt |
Score |
Points |
Result |
Date |
{(row.attempts ?? []).map((a) => (
| #{a.attempt_number} |
{a.score}% |
{a.earned_points}/{a.total_points} |
{a.passed
? Pass
: Fail}
|
{new Date(a.createdAt).toLocaleString()} |
))}
|
)}
>
);
}
function CompletionsTab({ completions, loading }) {
if (loading) return {[...Array(3)].map((_, i) => )}
;
if (!completions) return (
);
const { summary, completions: rows } = completions;
return (
{rows.length === 0 ? (
No one has attempted this assessment yet.
) : (
| Student |
Attempts |
Best Score |
Status |
Last Attempt |
|
{rows.map((row) => )}
)}
);
}
// ─── Sessions tab ─────────────────────────────────────────────────────────────
const SESSION_BADGE = {
completed: "bg-green-500/10 text-green-700 dark:text-green-400 border-green-500/20",
expired: "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/20",
in_progress: "bg-amber-500/10 text-amber-700 dark:text-amber-400 border-amber-500/20",
};
function fmtDuration(secs) {
if (secs == null) return "—";
if (secs < 60) return `${secs}s`;
const m = Math.floor(secs / 60);
const s = secs % 60;
return s > 0 ? `${m}m ${s}s` : `${m}m`;
}
function SessionsTab({ sessions, loading }) {
if (loading) return {[...Array(4)].map((_, i) => )}
;
if (!sessions) return (
);
const { summary, sessions: rows } = sessions;
return (
{rows.length === 0 ? (
No sessions recorded yet.
) : (
| Student |
Status |
Started |
Expires At |
Time Spent |
{rows.map((s) => (
|
{s.full_name}
{s.email}
|
{s.status === 'in_progress' ? 'In Progress' : s.status.charAt(0).toUpperCase() + s.status.slice(1)}
|
{new Date(s.started_at).toLocaleString()}
|
{s.expires_at ? new Date(s.expires_at).toLocaleString() : "—"}
|
{fmtDuration(s.time_spent_seconds)} |
))}
)}
);
}
function LoadingSkeleton() {
return (
{[...Array(3)].map((_, i) => )}
);
}
// ─── Page ─────────────────────────────────────────────────────────────────────
const TABS = [
{ key: "questions", label: "Questions", icon: ClipboardList },
{ key: "completions", label: "Completions", icon: Users },
{ key: "sessions", label: "Sessions", icon: Activity },
];
export default function ViewAssessment() {
const navigate = useNavigate();
const { courseId } = useParams();
const {
fetchAssessment, assessment,
fetchAssessmentCompletions, fetchAssessmentSessions,
completions, sessions,
loading,
} = useCourses();
const [activeTab, setActiveTab] = useState("questions");
useEffect(() => {
fetchAssessment(courseId);
}, [courseId]);
// Lazy-load completions/sessions the first time each tab is opened
const loadedRef = { completions: false, sessions: false };
useEffect(() => {
if (!assessment?.assessment_id) return;
if (activeTab === "completions") fetchAssessmentCompletions(courseId, assessment.assessment_id);
if (activeTab === "sessions") fetchAssessmentSessions(courseId, assessment.assessment_id);
}, [activeTab, assessment?.assessment_id]);
const questions = assessment?.questions ?? [];
const totalPoints = questions.reduce((sum, q) => sum + (q.points ?? 1), 0);
return (
{/* ── Header ── */}
View Assessment
{assessment && (
{questions.length} question{questions.length !== 1 ? "s" : ""} · {totalPoints} total point{totalPoints !== 1 ? "s" : ""}
)}
{/* Tabs */}
{assessment && (
{TABS.map(({ key, label, icon: Icon }) => (
))}
)}
{/* ── Content ── */}
{loading && !assessment ? (
) : !assessment ? (
No assessment has been created for this course yet.
) : activeTab === "questions" ? (
<>
{assessment.title || "Course Assessment"}
{assessment.is_required ? "Required" : "Optional"}
{assessment.passing_score ?? 70}%
{assessment.time_limit_minutes ? `${assessment.time_limit_minutes} mins` : "No limit"}
{questions.length}
{assessment.max_questions ? `${assessment.max_questions} (random)` : `All (${questions.length})`}
{totalPoints}
{assessment.max_attempts ?? 3}
{assessment.cooldown_hours ?? 24}h
Questions
{questions.length === 0 ? (
) : (
questions.map((q, i) =>
)
)}
>
) : activeTab === "completions" ? (
) : (
)}
);
}