import { createContext, useCallback, useContext, useState } from "react"; import api from "@/utils/api.util"; import { toast } from "sonner"; const CoursesContext = createContext(null); export function useCourses() { const ctx = useContext(CoursesContext); if (!ctx) throw new Error("useCourses must be used within a CoursesProvider"); return ctx; } // ─── Initial States ─────────────────────────────────────────────────────────── const PAGINATION_INIT = { page: 1, limit: 10, totalRecords: 0, totalPages: 0, hasPrevPage: false, hasNextPage: false, }; const BASE = "/admin/courses"; // Junction revamp — unit-scoped endpoints resolve either through a course // (/admin/courses/:courseId/units/:unitId) or straight against the standalone // unit library (/admin/units/:unitId) when no courseId is in scope. This lets // the quiz builder and unit pages run identically in both contexts. const unitBase = (courseId, unitId) => courseId != null && courseId !== "" ? `${BASE}/${courseId}/units/${unitId}` : `/admin/units/${unitId}`; // Lesson-scoped endpoints resolve through a unit when one is in scope, // otherwise straight against the standalone lesson library (/admin/lessons/:id). const lessonBase = (courseId, unitId, lessonId) => unitId != null && unitId !== "" ? `${unitBase(courseId, unitId)}/lessons/${lessonId}` : `/admin/lessons/${lessonId}`; // ─── Provider ───────────────────────────────────────────────────────────────── export function CoursesProvider({ children }) { // ─── State ──────────────────────────────────────────────────────────────── const [courses, setCourses] = useState([]); const [course, setCourse] = useState(null); const [units, setUnits] = useState([]); const [unit, setUnit] = useState(null); const [lessons, setLessons] = useState([]); const [lesson, setLesson] = useState(null); const [lessonPage, setLessonPage] = useState(null); const [prerequisites, setPrerequisites] = useState([]); const [quiz, setQuiz] = useState(null); const [questions, setQuestions] = useState([]); const [assessment, setAssessment] = useState(null); const [completions, setCompletions] = useState(null); // { summary, completions[] } const [sessions, setSessions] = useState(null); // { summary, sessions[] } const [attributes, setAttributes] = useState([]); const [pagination, setPagination] = useState(PAGINATION_INIT); const [loading, setLoading] = useState(false); // ─── Request wrapper ────────────────────────────────────────────────────── const request = useCallback(async (fn) => { setLoading(true); try { return await fn(); } catch (err) { const message = err?.response?.data?.message ?? "Something went wrong."; if (err.status === 404 && message) toast(message); else toast(message); return null; } finally { setLoading(false); } }, []); // ─── Paginated GET helper ───────────────────────────────────────────────── const paginatedGet = useCallback( (url, setter, params = {}) => request(async () => { const { filters, sort, ...rest } = params; const { data } = await api.get(url, { params: { ...rest, ...(filters?.length ? { filters: JSON.stringify(filters) } : {}), ...(sort?.length ? { sort: JSON.stringify(sort) } : {}), }, }); const final_data = data?.data; setter(final_data?.data ?? []); setPagination(final_data?.pagination ?? PAGINATION_INIT); if (final_data?.attributes?.length) setAttributes(final_data.attributes); return data; }), [request], ); // ========================================================================= // COURSES // ========================================================================= const fetchCourses = useCallback( (params = {}) => paginatedGet(BASE, setCourses, params), [paginatedGet], ); const fetchCourse = useCallback( (courseId) => request(async () => { const { data } = await api.get(`${BASE}/${courseId}`); const result = data?.data?.data ?? null; setCourse(result); setUnits(result?.units ?? []); setPrerequisites(result?.prerequisites ?? []); setAssessment(result?.assessment ?? null); return data; }), [request], ); const createCourse = useCallback( (payload) => request(async () => { const { data } = await api.post(BASE, payload); const course = data?.data?.data ?? null; if (course) { setCourses((prev) => [course, ...prev]); toast("Course created successfully."); } return data; }), [request], ); const updateCourse = useCallback( (courseId, payload) => request(async () => { const { data } = await api.put(`${BASE}/${courseId}`, payload); const course = data?.data?.data ?? null; if (course) { setCourses((prev) => prev.map((c) => (c.course_id === courseId ? course : c))); setCourse(course); toast("Course updated successfully."); } return data; }), [request], ); const archiveCourse = useCallback( (courseId) => request(async () => { const { data } = await api.delete(`${BASE}/${courseId}`); setCourses((prev) => prev.filter((c) => c.course_id !== courseId)); setCourse((prev) => (prev?.course_id === courseId ? null : prev)); toast("Course archived."); return data; }), [request], ); const archiveCourses = useCallback( ({ ids }) => request(async () => { const { data } = await api.delete(`${BASE}/bulk`, { data: { ids } }); setCourses((prev) => prev.filter((c) => !ids.includes(c.course_id))); toast("Courses archived."); return data; }), [request], ); // ── Course Archives & Restore ───────────────────────────────────────────── const fetchArchivedCourses = useCallback( (params = {}) => paginatedGet(`${BASE}/archives`, setCourses, params), [paginatedGet], ); const fetchArchivedCourse = useCallback( (courseId) => request(async () => { const { data } = await api.get(`${BASE}/archives/${courseId}`); const result = data?.data?.data ?? null; setCourse(result); return data; }), [request], ); const restoreCourse = useCallback( (courseId) => request(async () => { const { data } = await api.patch(`${BASE}/${courseId}/restore`); const result = data?.data?.data ?? null; if (result) { setCourses((prev) => prev.filter((c) => c.course_id !== courseId)); toast("Course restored."); } return data; }), [request], ); const restoreCourses = useCallback( ({ ids }) => request(async () => { const { data } = await api.patch(`${BASE}/restore/bulk`, { ids }); setCourses((prev) => prev.filter((c) => !ids.includes(c.course_id))); toast("Courses restored."); return data; }), [request], ); const permanentlyDeleteCourse = useCallback( (courseId) => request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/permanent`); setCourses((prev) => prev.filter((c) => c.course_id !== courseId)); setCourse((prev) => (prev?.course_id === courseId ? null : prev)); toast("Course permanently deleted."); return data; }), [request], ); const permanentlyDeleteCourses = useCallback( ({ ids }) => request(async () => { const { data } = await api.delete(`${BASE}/bulk/permanent`, { data: { ids } }); setCourses((prev) => prev.filter((c) => !ids.includes(c.course_id))); toast("Courses permanently deleted."); return data; }), [request], ); const fetchCoursePermanentDeleteImpact = useCallback( (courseId) => request(async () => { const { data } = await api.get(`${BASE}/${courseId}/permanent-delete-impact`); const { unitCount, lessonCount } = data?.data ?? {}; return [ { label: "unit(s)", count: unitCount ?? 0 }, { label: "lesson(s)", count: lessonCount ?? 0 }, ]; }), [request], ); // ========================================================================= // PREREQUISITES // ========================================================================= const fetchPrerequisites = useCallback( (courseId) => request(async () => { const { data } = await api.get(`${BASE}/${courseId}/prerequisites`); const result = data?.data?.data ?? []; setPrerequisites(result); return data; }), [request], ); const syncPrerequisites = useCallback( (courseId, prerequisites) => request(async () => { const { data } = await api.put(`${BASE}/${courseId}/prerequisites`, { prerequisites }); setPrerequisites(prerequisites); toast("Prerequisites updated."); return data; }), [request], ); // ========================================================================= // UNITS // ========================================================================= const fetchUnits = useCallback( (courseId, params = {}) => { const mergeQuiz = (incoming) => setUnits((prev) => { if (!prev.length) return incoming; const prevMap = Object.fromEntries(prev.map((u) => [u.unit_id, u])); return incoming.map((u) => { const old = prevMap[u.unit_id]; return { ...u, quiz: u.quiz ?? old?.quiz ?? null, quiz_id: u.quiz_id ?? old?.quiz_id ?? null, }; }); }); return paginatedGet(`${BASE}/${courseId}/units`, mergeQuiz, params); }, [paginatedGet], ); const fetchUnit = useCallback( (courseId, unitId) => request(async () => { const { data } = await api.get(`${unitBase(courseId, unitId)}`); const result = data?.data?.data ?? null; setUnit(result); setLessons(result?.lessons ?? []); setQuiz(result?.quiz ?? null); return data; }), [request], ); const createUnit = useCallback( (courseId, payload) => request(async () => { const { data } = await api.post(`${BASE}/${courseId}/units`, payload); const unit = data?.data?.data ?? null; if (unit) { setUnits((prev) => [...prev, unit]); toast("Unit created successfully."); } return data; }), [request], ); const updateUnit = useCallback( (courseId, unitId, payload) => request(async () => { const { data } = await api.put(`${unitBase(courseId, unitId)}`, payload); const unit = data?.data?.data ?? null; if (unit) { setUnits((prev) => prev.map((u) => (u.unit_id === unitId ? unit : u))); setUnit(unit); toast("Unit updated successfully."); } return data; }), [request], ); const archiveUnit = useCallback( (courseId, unitId) => request(async () => { const { data } = await api.delete(`${unitBase(courseId, unitId)}`); setUnits((prev) => prev.filter((u) => u.unit_id !== unitId)); setUnit((prev) => (prev?.unit_id === unitId ? null : prev)); toast("Unit archived."); return data; }), [request], ); const archiveUnits = useCallback( (courseId, { ids }) => request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/units/bulk`, { data: { ids } }); setUnits((prev) => prev.filter((u) => !ids.includes(u.unit_id))); toast("Units archived."); return data; }), [request], ); // ── Unit Archives & Restore ─────────────────────────────────────────────── const fetchArchivedUnits = useCallback( (courseId, params = {}) => paginatedGet(`${BASE}/${courseId}/units/archives`, setUnits, params), [paginatedGet], ); const fetchArchivedUnit = useCallback( (courseId, unitId) => request(async () => { const { data } = await api.get(`${BASE}/${courseId}/units/archives/${unitId}`); const result = data?.data?.data ?? null; setUnit(result); return data; }), [request], ); const restoreUnit = useCallback( (courseId, unitId) => request(async () => { const { data } = await api.patch(`${unitBase(courseId, unitId)}/restore`); const result = data?.data?.data ?? null; if (result) { setUnits((prev) => prev.filter((u) => u.unit_id !== unitId)); toast("Unit restored."); } return data; }), [request], ); const restoreUnits = useCallback( (courseId, { ids }) => request(async () => { const { data } = await api.patch(`${BASE}/${courseId}/units/restore/bulk`, { ids } ); setUnits((prev) => prev.filter((u) => !ids.includes(u.unit_id))); toast("Units restored."); return data; }), [request], ); const permanentlyDeleteUnit = useCallback( (courseId, unitId) => request(async () => { const { data } = await api.delete(`${unitBase(courseId, unitId)}/permanent`); setUnits((prev) => prev.filter((u) => u.unit_id !== unitId)); setUnit((prev) => (prev?.unit_id === unitId ? null : prev)); toast("Unit permanently deleted."); return data; }), [request], ); const permanentlyDeleteUnits = useCallback( (courseId, { ids }) => request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/units/bulk/permanent`, { data: { ids } }); setUnits((prev) => prev.filter((u) => !ids.includes(u.unit_id))); toast("Units permanently deleted."); return data; }), [request], ); const fetchUnitPermanentDeleteImpact = useCallback( (courseId, unitId) => request(async () => { const { data } = await api.get(`${unitBase(courseId, unitId)}/permanent-delete-impact`); const { lessonCount } = data?.data ?? {}; return [{ label: "lesson(s)", count: lessonCount ?? 0 }]; }), [request], ); // ========================================================================= // UNIT QUIZ // ========================================================================= const fetchQuiz = useCallback( (courseId, unitId) => request(async () => { const { data } = await api.get(`${unitBase(courseId, unitId)}/quiz`); const result = data?.data?.data ?? null; setQuiz(result); setQuestions(result?.questions ?? []); return data; }), [request], ); const createQuiz = useCallback( (courseId, unitId, payload) => request(async () => { const { data } = await api.post(`${unitBase(courseId, unitId)}/quiz`, payload); const result = data?.data?.data ?? null; if (result) { setQuiz(result); toast("Quiz created."); } return data; }), [request], ); const updateQuiz = useCallback( (courseId, unitId, quizId, payload) => request(async () => { const { data } = await api.patch(`${unitBase(courseId, unitId)}/quiz/${quizId}`, payload); const result = data?.data?.data ?? null; if (result) { setQuiz(result); toast("Quiz updated."); } return data; }), [request], ); const deleteQuiz = useCallback( (courseId, unitId, quizId, deletedBy) => request(async () => { const { data } = await api.delete(`${unitBase(courseId, unitId)}/quiz/${quizId}`, { data: { deletedBy } }); setQuiz(null); setQuestions([]); toast("Quiz archived."); return data; }), [request], ); // ── Quiz Archives & Restore ─────────────────────────────────────────────── const fetchArchivedQuiz = useCallback( (courseId, unitId) => request(async () => { const { data } = await api.get(`${unitBase(courseId, unitId)}/quiz/archives`); const result = data?.data?.data ?? null; setQuiz(result); return data; }), [request], ); const restoreQuiz = useCallback( (courseId, unitId, quizId, restoredBy) => request(async () => { const { data } = await api.patch(`${unitBase(courseId, unitId)}/quiz/${quizId}/restore`, { restoredBy }); const result = data?.data?.data ?? null; if (result) { setQuiz(result); toast("Quiz restored."); } return data; }), [request], ); // ========================================================================= // QUIZ QUESTIONS // ========================================================================= const fetchQuizQuestions = useCallback( (courseId, unitId, quizId) => request(async () => { const { data } = await api.get(`${unitBase(courseId, unitId)}/quiz/${quizId}/questions`); const result = data?.data?.data ?? []; setQuestions(result); return data; }), [request], ); const createQuizQuestion = useCallback( (courseId, unitId, quizId, payload) => request(async () => { const { data } = await api.post(`${unitBase(courseId, unitId)}/quiz/${quizId}/questions`, payload); const result = data?.data?.data ?? null; if (result) { setQuestions((prev) => [...prev, result]); toast("Question added."); } return data; }), [request], ); const updateQuizQuestion = useCallback( (courseId, unitId, quizId, questionId, payload) => request(async () => { const { data } = await api.patch(`${unitBase(courseId, unitId)}/quiz/${quizId}/questions/${questionId}`, payload); const result = data?.data?.data ?? null; if (result) { setQuestions((prev) => prev.map((q) => (q.question_id === questionId ? result : q))); toast("Question updated."); } return data; }), [request], ); const bulkSyncQuizQuestions = useCallback( (courseId, unitId, quizId, questions, updatedBy) => request(async () => { const { data } = await api.put(`${unitBase(courseId, unitId)}/quiz/${quizId}/questions/bulk-sync`, { questions, updatedBy }); const result = data?.data?.data ?? []; setQuestions(result); toast("Quiz saved."); return data; }), [request], ); const deleteQuizQuestion = useCallback( (courseId, unitId, quizId, questionId, deletedBy) => request(async () => { const { data } = await api.delete(`${unitBase(courseId, unitId)}/quiz/${quizId}/questions/${questionId}`, { data: { deletedBy } }); setQuestions((prev) => prev.filter((q) => q.question_id !== questionId)); toast("Question archived."); return data; }), [request], ); const bulkArchiveQuizQuestions = useCallback( (courseId, unitId, quizId, ids, deletedBy) => request(async () => { const { data } = await api.delete(`${unitBase(courseId, unitId)}/quiz/${quizId}/questions/bulk`, { data: { ids, deletedBy } }); setQuestions((prev) => prev.filter((q) => !ids.includes(q.question_id))); toast("Questions archived."); return data; }), [request], ); // ── Quiz Question Archives & Restore ───────────────────────────────────── const fetchArchivedQuizQuestion = useCallback( (courseId, unitId, quizId, questionId) => request(async () => { const { data } = await api.get(`${unitBase(courseId, unitId)}/quiz/${quizId}/questions/archives/${questionId}`); return data; }), [request], ); const restoreQuizQuestion = useCallback( (courseId, unitId, quizId, questionId, restoredBy) => request(async () => { const { data } = await api.patch(`${unitBase(courseId, unitId)}/quiz/${quizId}/questions/${questionId}/restore`, { restoredBy }); toast("Question restored."); return data; }), [request], ); const bulkRestoreQuizQuestions = useCallback( (courseId, unitId, quizId, ids, restoredBy) => request(async () => { const { data } = await api.patch(`${unitBase(courseId, unitId)}/quiz/${quizId}/questions/restore/bulk`, { ids, restoredBy }); toast("Questions restored."); return data; }), [request], ); // ========================================================================= // LESSONS // ========================================================================= const fetchLessons = useCallback( (courseId, unitId, params = {}) => paginatedGet(`${unitBase(courseId, unitId)}/lessons`, setLessons, params), [paginatedGet], ); const fetchLesson = useCallback( (courseId, unitId, lessonId) => request(async () => { const { data } = await api.get(`${lessonBase(courseId, unitId, lessonId)}`); const result = data?.data?.data ?? null; setLesson(result); setLessonPage(result?.page ?? null); return data; }), [request], ); const createLesson = useCallback( (courseId, unitId, payload) => request(async () => { const { data } = await api.post(`${unitBase(courseId, unitId)}/lessons`, payload); const lesson = data?.data?.data ?? null; if (lesson) { setLessons((prev) => [...prev, lesson]); toast("Lesson created successfully."); } return data; }), [request], ); const updateLesson = useCallback( (courseId, unitId, lessonId, payload) => request(async () => { const { data } = await api.put(`${lessonBase(courseId, unitId, lessonId)}`, payload); const lesson = data?.data?.data ?? null; if (lesson) { setLessons((prev) => prev.map((l) => (l.lesson_id === lessonId ? lesson : l))); setLesson(lesson); toast("Lesson updated successfully."); } return data; }), [request], ); const archiveLesson = useCallback( (courseId, unitId, lessonId) => request(async () => { const { data } = await api.delete(`${lessonBase(courseId, unitId, lessonId)}`); setLessons((prev) => prev.filter((l) => l.lesson_id !== lessonId)); setLesson((prev) => (prev?.lesson_id === lessonId ? null : prev)); toast("Lesson archived."); return data; }), [request], ); const archiveLessons = useCallback( (courseId, unitId, { ids }) => request(async () => { const { data } = await api.delete(`${unitBase(courseId, unitId)}/lessons/bulk`, { data: { ids } }); setLessons((prev) => prev.filter((l) => !ids.includes(l.lesson_id))); toast("Lessons archived."); return data; }), [request], ); // ── Lesson Archives & Restore ───────────────────────────────────────────── const fetchArchivedLessons = useCallback( (courseId, unitId, params = {}) => paginatedGet(`${unitBase(courseId, unitId)}/lessons/archives`, setLessons, params), [paginatedGet], ); const fetchArchivedLesson = useCallback( (courseId, unitId, lessonId) => request(async () => { const { data } = await api.get(`${unitBase(courseId, unitId)}/lessons/archives/${lessonId}`); const result = data?.data?.data ?? null; setLesson(result); return data; }), [request], ); const restoreLesson = useCallback( (courseId, unitId, lessonId) => request(async () => { const { data } = await api.patch(`${lessonBase(courseId, unitId, lessonId)}/restore`); const result = data?.data?.data ?? null; if (result) { setLessons((prev) => prev.filter((l) => l.lesson_id !== lessonId)); toast("Lesson restored."); } return data; }), [request], ); const restoreLessons = useCallback( (courseId, unitId, { ids }) => request(async () => { const { data } = await api.patch(`${unitBase(courseId, unitId)}/lessons/restore/bulk`, { ids }); setLessons((prev) => prev.filter((l) => !ids.includes(l.lesson_id))); toast("Lessons restored."); return data; }), [request], ); const permanentlyDeleteLesson = useCallback( (courseId, unitId, lessonId) => request(async () => { const { data } = await api.delete(`${lessonBase(courseId, unitId, lessonId)}/permanent`); setLessons((prev) => prev.filter((l) => l.lesson_id !== lessonId)); setLesson((prev) => (prev?.lesson_id === lessonId ? null : prev)); toast("Lesson permanently deleted."); return data; }), [request], ); const permanentlyDeleteLessons = useCallback( (courseId, unitId, { ids }) => request(async () => { const { data } = await api.delete(`${unitBase(courseId, unitId)}/lessons/bulk/permanent`, { data: { ids } }); setLessons((prev) => prev.filter((l) => !ids.includes(l.lesson_id))); toast("Lessons permanently deleted."); return data; }), [request], ); // ========================================================================= // LESSON PAGE // ========================================================================= const fetchLessonPage = useCallback( (courseId, unitId, lessonId) => request(async () => { const { data } = await api.get(`${lessonBase(courseId, unitId, lessonId)}/page`); const page = data?.data?.data ?? null; setLessonPage(page); return data; }), [request], ); const saveLessonPage = useCallback( (courseId, unitId, lessonId, payload) => request(async () => { const { data } = await api.put(`${lessonBase(courseId, unitId, lessonId)}/page`, payload); const page = data?.data?.data ?? null; if (page) { setLessonPage(page); toast("Lesson page saved."); } return data; }), [request], ); // ========================================================================= // COURSE ASSESSMENT // ========================================================================= const fetchAssessment = useCallback( (courseId) => request(async () => { const { data } = await api.get(`${BASE}/${courseId}/assessment`); const result = data?.data?.data ?? null; setAssessment(result); setQuestions(result?.questions ?? []); return data; }), [request], ); const createAssessment = useCallback( (courseId, payload) => request(async () => { const { data } = await api.post(`${BASE}/${courseId}/assessment`, payload); const result = data?.data?.data ?? null; if (result) { setAssessment(result); toast("Assessment created."); } return data; }), [request], ); const updateAssessment = useCallback( (courseId, assessmentId, payload) => request(async () => { const { data } = await api.patch(`${BASE}/${courseId}/assessment/${assessmentId}`, payload); const result = data?.data?.data ?? null; if (result) { setAssessment(result); toast("Assessment updated."); } return data; }), [request], ); const deleteAssessment = useCallback( (courseId, assessmentId, deletedBy) => request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/assessment/${assessmentId}`, { data: { deletedBy } }); setAssessment(null); setQuestions([]); toast("Assessment archived."); return data; }), [request], ); // ── Assessment Archives & Restore ───────────────────────────────────────── const fetchArchivedAssessment = useCallback( (courseId) => request(async () => { const { data } = await api.get(`${BASE}/${courseId}/assessment/archives`); const result = data?.data?.data ?? null; setAssessment(result); return data; }), [request], ); const restoreAssessment = useCallback( (courseId, assessmentId, restoredBy) => request(async () => { const { data } = await api.patch(`${BASE}/${courseId}/assessment/${assessmentId}/restore`, { restoredBy }); const result = data?.data?.data ?? null; if (result) { setAssessment(result); toast("Assessment restored."); } return data; }), [request], ); // ========================================================================= // ASSESSMENT QUESTIONS // ========================================================================= const fetchAssessmentQuestions = useCallback( (courseId, assessmentId) => request(async () => { const { data } = await api.get(`${BASE}/${courseId}/assessment/${assessmentId}/questions`); const result = data?.data?.data ?? []; setQuestions(result); return data; }), [request], ); const createAssessmentQuestion = useCallback( (courseId, assessmentId, payload) => request(async () => { const { data } = await api.post(`${BASE}/${courseId}/assessment/${assessmentId}/questions`, payload); const result = data?.data?.data ?? null; if (result) { setQuestions((prev) => [...prev, result]); toast("Question added."); } return data; }), [request], ); const updateAssessmentQuestion = useCallback( (courseId, assessmentId, questionId, payload) => request(async () => { const { data } = await api.patch(`${BASE}/${courseId}/assessment/${assessmentId}/questions/${questionId}`, payload); const result = data?.data?.data ?? null; if (result) { setQuestions((prev) => prev.map((q) => (q.question_id === questionId ? result : q))); toast("Question updated."); } return data; }), [request], ); const bulkSyncAssessmentQuestions = useCallback( (courseId, assessmentId, questions, updatedBy) => request(async () => { const { data } = await api.put(`${BASE}/${courseId}/assessment/${assessmentId}/questions/bulk-sync`, { questions, updatedBy }); const result = data?.data?.data ?? []; setQuestions(result); toast("Assessment saved."); return data; }), [request], ); const deleteAssessmentQuestion = useCallback( (courseId, assessmentId, questionId, deletedBy) => request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/assessment/${assessmentId}/questions/${questionId}`, { data: { deletedBy } }); setQuestions((prev) => prev.filter((q) => q.question_id !== questionId)); toast("Question archived."); return data; }), [request], ); const bulkArchiveAssessmentQuestions = useCallback( (courseId, assessmentId, ids, deletedBy) => request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/assessment/${assessmentId}/questions/bulk`, { data: { ids, deletedBy } }); setQuestions((prev) => prev.filter((q) => !ids.includes(q.question_id))); toast("Questions archived."); return data; }), [request], ); // ── Assessment Question Archives & Restore ──────────────────────────────── const fetchArchivedAssessmentQuestion = useCallback( (courseId, assessmentId, questionId) => request(async () => { const { data } = await api.get(`${BASE}/${courseId}/assessment/${assessmentId}/questions/archives/${questionId}`); return data; }), [request], ); const restoreAssessmentQuestion = useCallback( (courseId, assessmentId, questionId, restoredBy) => request(async () => { const { data } = await api.patch(`${BASE}/${courseId}/assessment/${assessmentId}/questions/${questionId}/restore`, { restoredBy }); toast("Question restored."); return data; }), [request], ); const bulkRestoreAssessmentQuestions = useCallback( (courseId, assessmentId, ids, restoredBy) => request(async () => { const { data } = await api.patch(`${BASE}/${courseId}/assessment/${assessmentId}/questions/restore/bulk`, { ids, restoredBy }); toast("Questions restored."); return data; }), [request], ); // ========================================================================= // COMPLETIONS & SESSIONS // ========================================================================= const fetchQuizCompletions = useCallback( (courseId, unitId, quizId) => request(async () => { setCompletions(null); const { data } = await api.get(`${unitBase(courseId, unitId)}/quiz/${quizId}/completions`); setCompletions(data?.data ?? null); return data; }), [request], ); const fetchAssessmentCompletions = useCallback( (courseId, assessmentId) => request(async () => { setCompletions(null); const { data } = await api.get(`${BASE}/${courseId}/assessment/${assessmentId}/completions`); setCompletions(data?.data ?? null); return data; }), [request], ); const fetchAssessmentSessions = useCallback( (courseId, assessmentId) => request(async () => { setSessions(null); const { data } = await api.get(`${BASE}/${courseId}/assessment/${assessmentId}/sessions`); setSessions(data?.data ?? null); return data; }), [request], ); // ========================================================================= // COURSE PRODUCT & CATEGORIES // ========================================================================= const fetchCourseProduct = useCallback( (courseId) => request(async () => { const { data } = await api.get(`/admin/products/courses/${courseId}/product`); return data.data ?? null; }), [request], ); const saveCourseProduct = useCallback( (courseId, payload) => request(async () => { const { data } = await api.put(`/admin/products/courses/${courseId}/product`, payload); toast("Product listing saved."); return data.data ?? null; }), [request], ); const removeCourseProduct = useCallback( (courseId) => request(async () => { await api.delete(`/admin/products/courses/${courseId}/product`); toast("Product listing removed."); return true; }), [request], ); const fetchCourseCategories = useCallback( (courseId) => request(async () => { const { data } = await api.get(`/admin/products/courses/${courseId}/categories`); return data.data ?? []; }), [request], ); const syncCourseCategories = useCallback( (courseId, categoryIds) => request(async () => { const { data } = await api.post(`/admin/products/courses/${courseId}/categories`, { category_ids: categoryIds }); toast("Categories updated."); return data.data ?? []; }), [request], ); const fetchInstructors = useCallback( (courseId) => request(async () => { const { data } = await api.get(`${BASE}/${courseId}/instructors`); return data.data ?? []; }), [request], ); const syncInstructors = useCallback( (courseId, instructors) => request(async () => { await api.put(`${BASE}/${courseId}/instructors`, { instructors }); toast("Instructors updated."); }), [request], ); const fetchCourseAchievements = useCallback( (courseId) => request(async () => { const { data } = await api.get(`${BASE}/${courseId}/achievements`); return (data?.data?.data ?? []).map((r) => r.achievement_key); }), [request], ); const syncCourseAchievements = useCallback( (courseId, achievement_keys) => request(async () => { await api.put(`${BASE}/${courseId}/achievements`, { achievement_keys }); toast("Rewards updated."); }), [request], ); const fetchCourseFieldValues = useCallback( (field) => request(async () => { const { data } = await api.get(`${BASE}/field-values`, { params: { field } }); const result = data?.data ?? []; return result; }), [request], ); const fetchUnitFieldValues = useCallback( (courseId, field) => request(async () => { const { data } = await api.get(`${BASE}/${courseId}/field-values`, { params: { field } }); const result = data?.data ?? []; return result; }), [request], ); const fetchLessonFieldValues = useCallback( (courseId, unitId, field) => request(async () => { const { data } = await api.get(`${unitBase(courseId, unitId)}/field-values`, { params: { field } }); const result = data?.data ?? []; return result; }), [request], ); // ─── Provider value ─────────────────────────────────────────────────────── return ( {children} ); }