Files
starr-philproperties/src/contexts/AdminCoursesContext.jsx
T
kennethobsequio b0f40e5133 add: more things
Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
2026-07-04 03:35:22 +08:00

1275 lines
44 KiB
React

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 (
<CoursesContext.Provider value={{
// ── state ──────────────────────────────────────────────────────────────
courses, course,
units, unit,
lessons, lesson,
lessonPage,
prerequisites,
quiz,
questions,
assessment,
attributes,
pagination,
loading,
// ── setters ────────────────────────────────────────────────────────────
setCourse,
setUnit,
setLesson,
setLessonPage,
setPrerequisites,
setQuiz,
setQuestions,
setAssessment,
setPagination,
// ── courses ────────────────────────────────────────────────────────────
fetchCourses,
fetchCourse,
createCourse,
updateCourse,
archiveCourse,
archiveCourses,
// ── course archives & restore ──────────────────────────────────────────
fetchArchivedCourses,
fetchArchivedCourse,
restoreCourse,
restoreCourses,
permanentlyDeleteCourse,
permanentlyDeleteCourses,
fetchCoursePermanentDeleteImpact,
// ── prerequisites ──────────────────────────────────────────────────────
fetchPrerequisites,
syncPrerequisites,
// ── units ──────────────────────────────────────────────────────────────
fetchUnits,
fetchUnit,
createUnit,
updateUnit,
archiveUnit,
archiveUnits,
// ── unit archives & restore ────────────────────────────────────────────
fetchArchivedUnits,
fetchArchivedUnit,
restoreUnit,
restoreUnits,
permanentlyDeleteUnit,
permanentlyDeleteUnits,
fetchUnitPermanentDeleteImpact,
// ── unit quiz ──────────────────────────────────────────────────────────
fetchQuiz,
createQuiz,
updateQuiz,
deleteQuiz,
// ── quiz archives & restore ────────────────────────────────────────────
fetchArchivedQuiz,
restoreQuiz,
// ── quiz questions ─────────────────────────────────────────────────────
fetchQuizQuestions,
createQuizQuestion,
updateQuizQuestion,
deleteQuizQuestion,
bulkArchiveQuizQuestions,
bulkSyncQuizQuestions,
// ── quiz question archives & restore ───────────────────────────────────
fetchArchivedQuizQuestion,
restoreQuizQuestion,
bulkRestoreQuizQuestions,
// ── lessons ────────────────────────────────────────────────────────────
fetchLessons,
fetchLesson,
createLesson,
updateLesson,
archiveLesson,
archiveLessons,
// ── lesson archives & restore ──────────────────────────────────────────
fetchArchivedLessons,
fetchArchivedLesson,
restoreLesson,
restoreLessons,
permanentlyDeleteLesson,
permanentlyDeleteLessons,
// ── lesson page ────────────────────────────────────────────────────────
fetchLessonPage,
saveLessonPage,
// ── completions & sessions ─────────────────────────────────────────────
completions, sessions,
fetchQuizCompletions,
fetchAssessmentCompletions,
fetchAssessmentSessions,
// ── assessment ─────────────────────────────────────────────────────────
fetchAssessment,
createAssessment,
updateAssessment,
deleteAssessment,
// ── assessment archives & restore ──────────────────────────────────────
fetchArchivedAssessment,
restoreAssessment,
// ── assessment questions ───────────────────────────────────────────────
fetchAssessmentQuestions,
createAssessmentQuestion,
updateAssessmentQuestion,
deleteAssessmentQuestion,
bulkArchiveAssessmentQuestions,
bulkSyncAssessmentQuestions,
// ── assessment question archives & restore ─────────────────────────────
fetchArchivedAssessmentQuestion,
restoreAssessmentQuestion,
bulkRestoreAssessmentQuestions,
// ── fetch field values ─────────────────────────────
fetchCourseFieldValues,
fetchUnitFieldValues,
fetchLessonFieldValues,
// ── course product & categories ────────────────────
fetchCourseProduct,
saveCourseProduct,
removeCourseProduct,
fetchCourseCategories,
syncCourseCategories,
fetchInstructors,
syncInstructors,
fetchCourseAchievements,
syncCourseAchievements,
}}>
{children}
</CoursesContext.Provider>
);
}