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"; // ─── 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.warning(message); else toast.error(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.success("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.success("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.success("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.success("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.success("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.success("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.success("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.success("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.success("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(`${BASE}/${courseId}/units/${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.success("Unit created successfully."); } return data; }), [request], ); const updateUnit = useCallback( (courseId, unitId, payload) => request(async () => { const { data } = await api.put(`${BASE}/${courseId}/units/${unitId}`, payload); const unit = data?.data?.data ?? null; if (unit) { setUnits((prev) => prev.map((u) => (u.unit_id === unitId ? unit : u))); setUnit(unit); toast.success("Unit updated successfully."); } return data; }), [request], ); const archiveUnit = useCallback( (courseId, unitId) => request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}`); setUnits((prev) => prev.filter((u) => u.unit_id !== unitId)); setUnit((prev) => (prev?.unit_id === unitId ? null : prev)); toast.success("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.success("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(`${BASE}/${courseId}/units/${unitId}/restore`); const result = data?.data?.data ?? null; if (result) { setUnits((prev) => prev.filter((u) => u.unit_id !== unitId)); toast.success("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.success("Units restored."); return data; }), [request], ); const permanentlyDeleteUnit = useCallback( (courseId, unitId) => request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/permanent`); setUnits((prev) => prev.filter((u) => u.unit_id !== unitId)); setUnit((prev) => (prev?.unit_id === unitId ? null : prev)); toast.success("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.success("Units permanently deleted."); return data; }), [request], ); const fetchUnitPermanentDeleteImpact = useCallback( (courseId, unitId) => request(async () => { const { data } = await api.get(`${BASE}/${courseId}/units/${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(`${BASE}/${courseId}/units/${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(`${BASE}/${courseId}/units/${unitId}/quiz`, payload); const result = data?.data?.data ?? null; if (result) { setQuiz(result); toast.success("Quiz created."); } return data; }), [request], ); const updateQuiz = useCallback( (courseId, unitId, quizId, payload) => request(async () => { const { data } = await api.patch(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}`, payload); const result = data?.data?.data ?? null; if (result) { setQuiz(result); toast.success("Quiz updated."); } return data; }), [request], ); const deleteQuiz = useCallback( (courseId, unitId, quizId, deletedBy) => request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}`, { data: { deletedBy } }); setQuiz(null); setQuestions([]); toast.success("Quiz archived."); return data; }), [request], ); // ── Quiz Archives & Restore ─────────────────────────────────────────────── const fetchArchivedQuiz = useCallback( (courseId, unitId) => request(async () => { const { data } = await api.get(`${BASE}/${courseId}/units/${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(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}/restore`, { restoredBy }); const result = data?.data?.data ?? null; if (result) { setQuiz(result); toast.success("Quiz restored."); } return data; }), [request], ); // ========================================================================= // QUIZ QUESTIONS // ========================================================================= const fetchQuizQuestions = useCallback( (courseId, unitId, quizId) => request(async () => { const { data } = await api.get(`${BASE}/${courseId}/units/${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(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}/questions`, payload); const result = data?.data?.data ?? null; if (result) { setQuestions((prev) => [...prev, result]); toast.success("Question added."); } return data; }), [request], ); const updateQuizQuestion = useCallback( (courseId, unitId, quizId, questionId, payload) => request(async () => { const { data } = await api.patch(`${BASE}/${courseId}/units/${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.success("Question updated."); } return data; }), [request], ); const bulkSyncQuizQuestions = useCallback( (courseId, unitId, quizId, questions, updatedBy) => request(async () => { const { data } = await api.put(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}/questions/bulk-sync`, { questions, updatedBy }); const result = data?.data?.data ?? []; setQuestions(result); toast.success("Quiz saved."); return data; }), [request], ); const deleteQuizQuestion = useCallback( (courseId, unitId, quizId, questionId, deletedBy) => request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}/questions/${questionId}`, { data: { deletedBy } }); setQuestions((prev) => prev.filter((q) => q.question_id !== questionId)); toast.success("Question archived."); return data; }), [request], ); const bulkArchiveQuizQuestions = useCallback( (courseId, unitId, quizId, ids, deletedBy) => request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}/questions/bulk`, { data: { ids, deletedBy } }); setQuestions((prev) => prev.filter((q) => !ids.includes(q.question_id))); toast.success("Questions archived."); return data; }), [request], ); // ── Quiz Question Archives & Restore ───────────────────────────────────── const fetchArchivedQuizQuestion = useCallback( (courseId, unitId, quizId, questionId) => request(async () => { const { data } = await api.get(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}/questions/archives/${questionId}`); return data; }), [request], ); const restoreQuizQuestion = useCallback( (courseId, unitId, quizId, questionId, restoredBy) => request(async () => { const { data } = await api.patch(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}/questions/${questionId}/restore`, { restoredBy }); toast.success("Question restored."); return data; }), [request], ); const bulkRestoreQuizQuestions = useCallback( (courseId, unitId, quizId, ids, restoredBy) => request(async () => { const { data } = await api.patch(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}/questions/restore/bulk`, { ids, restoredBy }); toast.success("Questions restored."); return data; }), [request], ); // ========================================================================= // LESSONS // ========================================================================= const fetchLessons = useCallback( (courseId, unitId, params = {}) => paginatedGet(`${BASE}/${courseId}/units/${unitId}/lessons`, setLessons, params), [paginatedGet], ); const fetchLesson = useCallback( (courseId, unitId, lessonId) => request(async () => { const { data } = await api.get(`${BASE}/${courseId}/units/${unitId}/lessons/${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(`${BASE}/${courseId}/units/${unitId}/lessons`, payload); const lesson = data?.data?.data ?? null; if (lesson) { setLessons((prev) => [...prev, lesson]); toast.success("Lesson created successfully."); } return data; }), [request], ); const updateLesson = useCallback( (courseId, unitId, lessonId, payload) => request(async () => { const { data } = await api.put(`${BASE}/${courseId}/units/${unitId}/lessons/${lessonId}`, payload); const lesson = data?.data?.data ?? null; if (lesson) { setLessons((prev) => prev.map((l) => (l.lesson_id === lessonId ? lesson : l))); setLesson(lesson); toast.success("Lesson updated successfully."); } return data; }), [request], ); const archiveLesson = useCallback( (courseId, unitId, lessonId) => request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/lessons/${lessonId}`); setLessons((prev) => prev.filter((l) => l.lesson_id !== lessonId)); setLesson((prev) => (prev?.lesson_id === lessonId ? null : prev)); toast.success("Lesson archived."); return data; }), [request], ); const archiveLessons = useCallback( (courseId, unitId, { ids }) => request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/lessons/bulk`, { data: { ids } }); setLessons((prev) => prev.filter((l) => !ids.includes(l.lesson_id))); toast.success("Lessons archived."); return data; }), [request], ); // ── Lesson Archives & Restore ───────────────────────────────────────────── const fetchArchivedLessons = useCallback( (courseId, unitId, params = {}) => paginatedGet(`${BASE}/${courseId}/units/${unitId}/lessons/archives`, setLessons, params), [paginatedGet], ); const fetchArchivedLesson = useCallback( (courseId, unitId, lessonId) => request(async () => { const { data } = await api.get(`${BASE}/${courseId}/units/${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(`${BASE}/${courseId}/units/${unitId}/lessons/${lessonId}/restore`); const result = data?.data?.data ?? null; if (result) { setLessons((prev) => prev.filter((l) => l.lesson_id !== lessonId)); toast.success("Lesson restored."); } return data; }), [request], ); const restoreLessons = useCallback( (courseId, unitId, { ids }) => request(async () => { const { data } = await api.patch(`${BASE}/${courseId}/units/${unitId}/lessons/restore/bulk`, { ids }); setLessons((prev) => prev.filter((l) => !ids.includes(l.lesson_id))); toast.success("Lessons restored."); return data; }), [request], ); const permanentlyDeleteLesson = useCallback( (courseId, unitId, lessonId) => request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/lessons/${lessonId}/permanent`); setLessons((prev) => prev.filter((l) => l.lesson_id !== lessonId)); setLesson((prev) => (prev?.lesson_id === lessonId ? null : prev)); toast.success("Lesson permanently deleted."); return data; }), [request], ); const permanentlyDeleteLessons = useCallback( (courseId, unitId, { ids }) => request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/lessons/bulk/permanent`, { data: { ids } }); setLessons((prev) => prev.filter((l) => !ids.includes(l.lesson_id))); toast.success("Lessons permanently deleted."); return data; }), [request], ); // ========================================================================= // LESSON PAGE // ========================================================================= const fetchLessonPage = useCallback( (courseId, unitId, lessonId) => request(async () => { const { data } = await api.get(`${BASE}/${courseId}/units/${unitId}/lessons/${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(`${BASE}/${courseId}/units/${unitId}/lessons/${lessonId}/page`, payload); const page = data?.data?.data ?? null; if (page) { setLessonPage(page); toast.success("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.success("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.success("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.success("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.success("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.success("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.success("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.success("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.success("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.success("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.success("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.success("Questions restored."); return data; }), [request], ); // ========================================================================= // COMPLETIONS & SESSIONS // ========================================================================= const fetchQuizCompletions = useCallback( (courseId, unitId, quizId) => request(async () => { setCompletions(null); const { data } = await api.get(`${BASE}/${courseId}/units/${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.success("Product listing saved."); return data.data ?? null; }), [request], ); const removeCourseProduct = useCallback( (courseId) => request(async () => { await api.delete(`/admin/products/courses/${courseId}/product`); toast.success("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.success("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.success("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.success("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(`${BASE}/${courseId}/units/${unitId}/field-values`, { params: { field } }); const result = data?.data ?? []; return result; }), [request], ); // ─── Provider value ─────────────────────────────────────────────────────── return ( {children} ); }