wip: course layout and func to context

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-05-16 11:05:04 +08:00
parent dcf5f43fd5
commit 2b1955608d
38 changed files with 3086 additions and 9 deletions
+395
View File
@@ -0,0 +1,395 @@
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";
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 [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.";
toast.error(message);
return null;
} finally {
setLoading(false);
}
}, []);
// =========================================================================
// ─── COURSES ─────────────────────────────────────────────────────────────
// =========================================================================
// ─── GET /admin/courses ───────────────────────────────────────────────────
const fetchCourses = useCallback(
(params = {}) =>
request(async () => {
const { filters, sort, ...rest } = params;
const { data } = await api.get(BASE, {
params: {
...rest,
...(filters?.length ? { filters: JSON.stringify(filters) } : {}),
...(sort?.length ? { sort: JSON.stringify(sort) } : {}),
},
});
const final_data = data?.data;
setCourses(final_data?.data ?? []);
setPagination(final_data?.pagination ?? PAGINATION_INIT);
if (final_data?.attributes?.length) setAttributes(final_data.attributes);
return data;
}),
[request]
);
// ─── GET /admin/courses/:courseId ─────────────────────────────────────────
const fetchCourse = useCallback(
(courseId) =>
request(async () => {
const { data } = await api.get(`${BASE}/${courseId}`);
const result = data?.data?.data ?? null;
setCourse(result);
setUnits(result?.units ?? []);
return data;
}),
[request]
);
// ─── POST /admin/courses ──────────────────────────────────────────────────
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]
);
// ─── PATCH /admin/courses/:courseId ───────────────────────────────────────
const updateCourse = useCallback(
(courseId, payload) =>
request(async () => {
const { data } = await api.patch(`${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]
);
// ─── DELETE /admin/courses/:courseId ──────────────────────────────────────
const deleteCourse = 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 deleted.");
return data;
}),
[request]
);
// =========================================================================
// ─── UNITS ───────────────────────────────────────────────────────────────
// =========================================================================
// ─── GET /admin/courses/:courseId/units ───────────────────────────────────
const fetchUnits = useCallback(
(courseId, params = {}) =>
request(async () => {
const { filters, sort, ...rest } = params;
const { data } = await api.get(`${BASE}/${courseId}/units`, {
params: {
...rest,
...(filters?.length ? { filters: JSON.stringify(filters) } : {}),
...(sort?.length ? { sort: JSON.stringify(sort) } : {}),
},
});
const final_data = data?.data;
setUnits(final_data?.data ?? []);
setPagination(final_data?.pagination ?? PAGINATION_INIT);
if (final_data?.attributes?.length) setAttributes(final_data.attributes);
return data;
}),
[request]
);
// ─── GET /admin/courses/:courseId/units/:unitId ───────────────────────────
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 ?? []);
return data;
}),
[request]
);
// ─── POST /admin/courses/:courseId/units ──────────────────────────────────
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]
);
// ─── PATCH /admin/courses/:courseId/units/:unitId ─────────────────────────
const updateUnit = useCallback(
(courseId, unitId, payload) =>
request(async () => {
const { data } = await api.patch(`${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]
);
// ─── DELETE /admin/courses/:courseId/units/:unitId ────────────────────────
const deleteUnit = 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 deleted.");
return data;
}),
[request]
);
// =========================================================================
// ─── LESSONS ─────────────────────────────────────────────────────────────
// =========================================================================
// ─── GET /admin/courses/:courseId/units/:unitId/lessons ───────────────────
const fetchLessons = useCallback(
(courseId, unitId, params = {}) =>
request(async () => {
const { filters, sort, ...rest } = params;
const { data } = await api.get(`${BASE}/${courseId}/units/${unitId}/lessons`, {
params: {
...rest,
...(filters?.length ? { filters: JSON.stringify(filters) } : {}),
...(sort?.length ? { sort: JSON.stringify(sort) } : {}),
},
});
const final_data = data?.data;
setLessons(final_data?.data ?? []);
setPagination(final_data?.pagination ?? PAGINATION_INIT);
if (final_data?.attributes?.length) setAttributes(final_data.attributes);
return data;
}),
[request]
);
// ─── GET /admin/courses/:courseId/units/:unitId/lessons/:lessonId ─────────
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]
);
// ─── POST /admin/courses/:courseId/units/:unitId/lessons ──────────────────
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]
);
// ─── PATCH /admin/courses/:courseId/units/:unitId/lessons/:lessonId ───────
const updateLesson = useCallback(
(courseId, unitId, lessonId, payload) =>
request(async () => {
const { data } = await api.patch(
`${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]
);
// ─── DELETE /admin/courses/:courseId/units/:unitId/lessons/:lessonId ──────
const deleteLesson = 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 deleted.");
return data;
}),
[request]
);
// =========================================================================
// ─── LESSON PAGE ─────────────────────────────────────────────────────────
// =========================================================================
// ─── GET .../lessons/:lessonId/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]
);
// ─── PUT .../lessons/:lessonId/page ───────────────────────────────────────
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]
);
// ─── Provider ─────────────────────────────────────────────────────────────
return (
<CoursesContext.Provider value={{
// state
courses, course,
units, unit,
lessons, lesson,
lessonPage,
attributes,
pagination,
loading,
// setters
setPagination,
setCourse,
setUnit,
setLesson,
setLessonPage,
// course actions
fetchCourses,
fetchCourse,
createCourse,
updateCourse,
deleteCourse,
// unit actions
fetchUnits,
fetchUnit,
createUnit,
updateUnit,
deleteUnit,
// lesson actions
fetchLessons,
fetchLesson,
createLesson,
updateLesson,
deleteLesson,
// lesson page actions
fetchLessonPage,
saveLessonPage,
}}>
{children}
</CoursesContext.Provider>
);
}