diff --git a/src/modules/admin/pages/courses/CourseAssessment.jsx b/src/modules/admin/components/courses/AssessmentEditor.jsx similarity index 87% rename from src/modules/admin/pages/courses/CourseAssessment.jsx rename to src/modules/admin/components/courses/AssessmentEditor.jsx index defc3d8..12825b0 100644 --- a/src/modules/admin/pages/courses/CourseAssessment.jsx +++ b/src/modules/admin/components/courses/AssessmentEditor.jsx @@ -1,19 +1,17 @@ import { useEffect, useRef, useState } from "react"; -import { useNavigate, useParams } from "react-router-dom"; -import { ArrowLeft, House, Plus, Save, ClipboardList, ChevronUp, ChevronDown, AlertTriangle, Trash2 } from "lucide-react"; +import { Plus, Save, ClipboardList, ChevronUp, ChevronDown, AlertTriangle, Trash2, X } from "lucide-react"; import { toast } from "sonner"; import { useCourses } from "@/contexts/AdminCoursesContext"; import { useAuth } from "@/contexts/AuthContext"; import { useUnsavedChangesGuard } from "@/hooks/useUnsavedChangesGuard"; -import { PageMeta } from "@/contexts/MetadataContext"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Spinner } from "@/components/ui/spinner"; import { Checkbox } from "@/components/ui/checkbox"; import { cn } from "@/lib/utils"; -import { QuestionCard, makeQuestion } from "../../components/courses/QuestionEditor"; +import { QuestionCard, makeQuestion } from "./QuestionEditor"; import api from "@/utils/api.util"; // ── Dirty-check snapshot ────────────────────────────────────────────────────── @@ -114,7 +112,7 @@ const TYPE_LABEL = { function QuestionNavigator({ questions, activeIndex, onJump, onMove, navItemRefs, navContainerRef, errors }) { return ( -
No questions yet. @@ -216,15 +214,13 @@ function QuestionNavigator({ questions, activeIndex, onJump, onMove, navItemRefs ); } -// ── Main Page ───────────────────────────────────────────────────────────────── +// ── Main ────────────────────────────────────────────────────────────────────── -export default function CourseAssessment() { - const navigate = useNavigate(); - const { courseId } = useParams(); +export default function AssessmentEditor({ courseId, onSaved, onCancel }) { const { createAssessment, updateAssessment, bulkSyncAssessmentQuestions, - course, loading, + loading, } = useCourses(); const [localAssessment, setLocalAssessment] = useState(null); @@ -258,7 +254,6 @@ export default function CourseAssessment() { const questionRefs = useRef([]); const navItemRefs = useRef([]); const navContainerRef = useRef(null); - const headerRef = useRef(null); // ── Fetch — silently treat 404 as "no assessment yet" (create mode) ───────── useEffect(() => { @@ -313,20 +308,6 @@ export default function CourseAssessment() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [initializing]); - // ── Measure sticky header → --assessment-h ──────────────────────────────── - useEffect(() => { - if (!headerRef.current) return; - const update = () => { - document.documentElement.style.setProperty( - "--assessment-h", - `${headerRef.current.offsetHeight}px` - ); - }; - update(); - window.addEventListener("resize", update); - return () => window.removeEventListener("resize", update); - }, []); - // ── Scroll to keep active nav item visible ───────────────────────────────── useEffect(() => { const item = navItemRefs.current[activeIndex]; @@ -363,15 +344,11 @@ export default function CourseAssessment() { return () => observers.forEach((o) => o.disconnect()); }, [questions.length]); - // ── Scroll helper with sticky offset ────────────────────────────────────── + // ── Scroll helper — a generous scroll-margin-top on each question keeps it + // clear of the page's sticky navbar/tab-bar above without needing to + // measure their heights. const scrollToQuestion = (index) => { - const el = questionRefs.current[index]; - if (!el) return; - const navbarH = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--navbar-h") || "0", 10); - const assessmentH = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--assessment-h") || "0", 10); - const offset = navbarH + assessmentH + 16; - const top = el.getBoundingClientRect().top + window.scrollY - offset; - window.scrollTo({ top, behavior: "smooth" }); + questionRefs.current[index]?.scrollIntoView({ behavior: "smooth", block: "start" }); }; // ── Question actions ─────────────────────────────────────────────────────── @@ -530,6 +507,7 @@ export default function CourseAssessment() { initialSnapshot.current = snapAssessment({ title, passingScore, timeLimit, isRequired, maxQuestions, maxAttempts, cooldownHours, shuffleQuestions, questions }); localStorage.removeItem(DRAFT_KEY); setDraftInfo(null); + onSaved?.(); }; const handleConfirmSave = async () => { @@ -542,65 +520,48 @@ export default function CourseAssessment() { // ── Render ───────────────────────────────────────────────────────────────── return ( -
- {questions.length} question{questions.length !== 1 ? "s" : ""} · {totalPoints} total point{totalPoints !== 1 ? "s" : ""} -
-- {questions.length} question{questions.length !== 1 ? "s" : ""} · {totalPoints} total point{totalPoints !== 1 ? "s" : ""} -
- )} -
+
+ {questions.length} question{questions.length !== 1 ? "s" : ""} · {totalPoints} total point{totalPoints !== 1 ? "s" : ""} +
)}No assessment has been created for this course yet.
-Questions
- {questions.length === 0 ? ( -No questions added yet.
-No assessment has been created for this course yet.
+Questions
+ {questions.length === 0 ? ( +No questions added yet.
++ Attach an existing library unit to reuse its content, or create a new one from scratch. +
++ Configure how learners complete this unit — optional, sensible defaults apply automatically. This is created together with the rest of the unit when you finish. +
+- Configure how learners complete this unit — optional, sensible defaults apply automatically. This is created together with the rest of the unit when you finish. -
-+ Attach existing library lessons to reuse content, or create new ones from scratch. +
+No lessons yet. A unit can be created without any, but add one now if you'd like to build its content in this wizard.
)} + {existingLessons.map((l) => ( +No lessons will be created with this unit.
) : ( @@ -409,7 +452,7 @@ function StepRequirements({ requirements, setRequirements }) { // ─── Main Page ────────────────────────────────────────────────────────────── export default function AddLibraryUnit() { const navigate = useNavigate(); - const { createUnitFull, loading } = useLibrary(); + const { createUnitFull, attachLessonsToUnit, lessonsFlat, loading } = useLibrary(); const { user } = useAuth(); const { syncUnitRequirements } = useCourses(); @@ -417,6 +460,8 @@ export default function AddLibraryUnit() { const [step, setStep] = useState(0); const [requirements, setRequirements] = useState([]); const [tierCategories, setTierCategories] = useState([]); + const [existingLessons, setExistingLessons] = useState([]); + const [attachLessonsOpen, setAttachLessonsOpen] = useState(false); useEffect(() => { api.get("/admin/tiers/categories") @@ -473,6 +518,10 @@ export default function AddLibraryUnit() { const newUnitId = result?.data?.data?.unit_id; if (!newUnitId) return; + if (existingLessons.length > 0) { + await attachLessonsToUnit(newUnitId, existingLessons.map((l) => l.lesson_id)); + } + if (requirements.length > 0) { const clean = requirements.map(({ _key, ...r }) => r); await syncUnitRequirements(null, newUnitId, clean); @@ -551,7 +600,16 @@ export default function AddLibraryUnit() { /> )} {step === 1 && ( -