add: ver()

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-07-08 11:31:54 +08:00
parent 6624c0d27f
commit 182bd93d10
46 changed files with 4407 additions and 98 deletions
+53 -37
View File
@@ -23,6 +23,22 @@ const PAGINATION_INIT = {
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 }) {
@@ -288,7 +304,7 @@ export function CoursesProvider({ children }) {
const fetchUnit = useCallback(
(courseId, unitId) =>
request(async () => {
const { data } = await api.get(`${BASE}/${courseId}/units/${unitId}`);
const { data } = await api.get(`${unitBase(courseId, unitId)}`);
const result = data?.data?.data ?? null;
setUnit(result);
setLessons(result?.lessons ?? []);
@@ -315,7 +331,7 @@ export function CoursesProvider({ children }) {
const updateUnit = useCallback(
(courseId, unitId, payload) =>
request(async () => {
const { data } = await api.put(`${BASE}/${courseId}/units/${unitId}`, payload);
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)));
@@ -330,7 +346,7 @@ export function CoursesProvider({ children }) {
const archiveUnit = useCallback(
(courseId, unitId) =>
request(async () => {
const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}`);
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.");
@@ -372,7 +388,7 @@ export function CoursesProvider({ children }) {
const restoreUnit = useCallback(
(courseId, unitId) =>
request(async () => {
const { data } = await api.patch(`${BASE}/${courseId}/units/${unitId}/restore`);
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));
@@ -397,7 +413,7 @@ export function CoursesProvider({ children }) {
const permanentlyDeleteUnit = useCallback(
(courseId, unitId) =>
request(async () => {
const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/permanent`);
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.");
@@ -420,7 +436,7 @@ export function CoursesProvider({ children }) {
const fetchUnitPermanentDeleteImpact = useCallback(
(courseId, unitId) =>
request(async () => {
const { data } = await api.get(`${BASE}/${courseId}/units/${unitId}/permanent-delete-impact`);
const { data } = await api.get(`${unitBase(courseId, unitId)}/permanent-delete-impact`);
const { lessonCount } = data?.data ?? {};
return [{ label: "lesson(s)", count: lessonCount ?? 0 }];
}),
@@ -434,7 +450,7 @@ export function CoursesProvider({ children }) {
const fetchQuiz = useCallback(
(courseId, unitId) =>
request(async () => {
const { data } = await api.get(`${BASE}/${courseId}/units/${unitId}/quiz`);
const { data } = await api.get(`${unitBase(courseId, unitId)}/quiz`);
const result = data?.data?.data ?? null;
setQuiz(result);
setQuestions(result?.questions ?? []);
@@ -446,7 +462,7 @@ export function CoursesProvider({ children }) {
const createQuiz = useCallback(
(courseId, unitId, payload) =>
request(async () => {
const { data } = await api.post(`${BASE}/${courseId}/units/${unitId}/quiz`, payload);
const { data } = await api.post(`${unitBase(courseId, unitId)}/quiz`, payload);
const result = data?.data?.data ?? null;
if (result) {
setQuiz(result);
@@ -460,7 +476,7 @@ export function CoursesProvider({ children }) {
const updateQuiz = useCallback(
(courseId, unitId, quizId, payload) =>
request(async () => {
const { data } = await api.patch(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}`, payload);
const { data } = await api.patch(`${unitBase(courseId, unitId)}/quiz/${quizId}`, payload);
const result = data?.data?.data ?? null;
if (result) {
setQuiz(result);
@@ -474,7 +490,7 @@ export function CoursesProvider({ children }) {
const deleteQuiz = useCallback(
(courseId, unitId, quizId, deletedBy) =>
request(async () => {
const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}`, { data: { deletedBy } });
const { data } = await api.delete(`${unitBase(courseId, unitId)}/quiz/${quizId}`, { data: { deletedBy } });
setQuiz(null);
setQuestions([]);
toast("Quiz archived.");
@@ -488,7 +504,7 @@ export function CoursesProvider({ children }) {
const fetchArchivedQuiz = useCallback(
(courseId, unitId) =>
request(async () => {
const { data } = await api.get(`${BASE}/${courseId}/units/${unitId}/quiz/archives`);
const { data } = await api.get(`${unitBase(courseId, unitId)}/quiz/archives`);
const result = data?.data?.data ?? null;
setQuiz(result);
return data;
@@ -499,7 +515,7 @@ export function CoursesProvider({ children }) {
const restoreQuiz = useCallback(
(courseId, unitId, quizId, restoredBy) =>
request(async () => {
const { data } = await api.patch(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}/restore`, { restoredBy });
const { data } = await api.patch(`${unitBase(courseId, unitId)}/quiz/${quizId}/restore`, { restoredBy });
const result = data?.data?.data ?? null;
if (result) {
setQuiz(result);
@@ -517,7 +533,7 @@ export function CoursesProvider({ children }) {
const fetchQuizQuestions = useCallback(
(courseId, unitId, quizId) =>
request(async () => {
const { data } = await api.get(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}/questions`);
const { data } = await api.get(`${unitBase(courseId, unitId)}/quiz/${quizId}/questions`);
const result = data?.data?.data ?? [];
setQuestions(result);
return data;
@@ -528,7 +544,7 @@ export function CoursesProvider({ children }) {
const createQuizQuestion = useCallback(
(courseId, unitId, quizId, payload) =>
request(async () => {
const { data } = await api.post(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}/questions`, payload);
const { data } = await api.post(`${unitBase(courseId, unitId)}/quiz/${quizId}/questions`, payload);
const result = data?.data?.data ?? null;
if (result) {
setQuestions((prev) => [...prev, result]);
@@ -542,7 +558,7 @@ export function CoursesProvider({ children }) {
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 { 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)));
@@ -556,7 +572,7 @@ export function CoursesProvider({ children }) {
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 { data } = await api.put(`${unitBase(courseId, unitId)}/quiz/${quizId}/questions/bulk-sync`, { questions, updatedBy });
const result = data?.data?.data ?? [];
setQuestions(result);
toast("Quiz saved.");
@@ -568,7 +584,7 @@ export function CoursesProvider({ children }) {
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 } });
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;
@@ -579,7 +595,7 @@ export function CoursesProvider({ children }) {
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 } });
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;
@@ -592,7 +608,7 @@ export function CoursesProvider({ children }) {
const fetchArchivedQuizQuestion = useCallback(
(courseId, unitId, quizId, questionId) =>
request(async () => {
const { data } = await api.get(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}/questions/archives/${questionId}`);
const { data } = await api.get(`${unitBase(courseId, unitId)}/quiz/${quizId}/questions/archives/${questionId}`);
return data;
}),
[request],
@@ -601,7 +617,7 @@ export function CoursesProvider({ children }) {
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 });
const { data } = await api.patch(`${unitBase(courseId, unitId)}/quiz/${quizId}/questions/${questionId}/restore`, { restoredBy });
toast("Question restored.");
return data;
}),
@@ -611,7 +627,7 @@ export function CoursesProvider({ children }) {
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 });
const { data } = await api.patch(`${unitBase(courseId, unitId)}/quiz/${quizId}/questions/restore/bulk`, { ids, restoredBy });
toast("Questions restored.");
return data;
}),
@@ -624,14 +640,14 @@ export function CoursesProvider({ children }) {
const fetchLessons = useCallback(
(courseId, unitId, params = {}) =>
paginatedGet(`${BASE}/${courseId}/units/${unitId}/lessons`, setLessons, params),
paginatedGet(`${unitBase(courseId, 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 { data } = await api.get(`${lessonBase(courseId, unitId, lessonId)}`);
const result = data?.data?.data ?? null;
setLesson(result);
setLessonPage(result?.page ?? null);
@@ -643,7 +659,7 @@ export function CoursesProvider({ children }) {
const createLesson = useCallback(
(courseId, unitId, payload) =>
request(async () => {
const { data } = await api.post(`${BASE}/${courseId}/units/${unitId}/lessons`, payload);
const { data } = await api.post(`${unitBase(courseId, unitId)}/lessons`, payload);
const lesson = data?.data?.data ?? null;
if (lesson) {
setLessons((prev) => [...prev, lesson]);
@@ -657,7 +673,7 @@ export function CoursesProvider({ children }) {
const updateLesson = useCallback(
(courseId, unitId, lessonId, payload) =>
request(async () => {
const { data } = await api.put(`${BASE}/${courseId}/units/${unitId}/lessons/${lessonId}`, payload);
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)));
@@ -672,7 +688,7 @@ export function CoursesProvider({ children }) {
const archiveLesson = useCallback(
(courseId, unitId, lessonId) =>
request(async () => {
const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/lessons/${lessonId}`);
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.");
@@ -684,7 +700,7 @@ export function CoursesProvider({ children }) {
const archiveLessons = useCallback(
(courseId, unitId, { ids }) =>
request(async () => {
const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/lessons/bulk`, { data: { ids } });
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;
@@ -696,14 +712,14 @@ export function CoursesProvider({ children }) {
const fetchArchivedLessons = useCallback(
(courseId, unitId, params = {}) =>
paginatedGet(`${BASE}/${courseId}/units/${unitId}/lessons/archives`, setLessons, params),
paginatedGet(`${unitBase(courseId, 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 { data } = await api.get(`${unitBase(courseId, unitId)}/lessons/archives/${lessonId}`);
const result = data?.data?.data ?? null;
setLesson(result);
return data;
@@ -714,7 +730,7 @@ export function CoursesProvider({ children }) {
const restoreLesson = useCallback(
(courseId, unitId, lessonId) =>
request(async () => {
const { data } = await api.patch(`${BASE}/${courseId}/units/${unitId}/lessons/${lessonId}/restore`);
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));
@@ -728,7 +744,7 @@ export function CoursesProvider({ children }) {
const restoreLessons = useCallback(
(courseId, unitId, { ids }) =>
request(async () => {
const { data } = await api.patch(`${BASE}/${courseId}/units/${unitId}/lessons/restore/bulk`, { ids });
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;
@@ -739,7 +755,7 @@ export function CoursesProvider({ children }) {
const permanentlyDeleteLesson = useCallback(
(courseId, unitId, lessonId) =>
request(async () => {
const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/lessons/${lessonId}/permanent`);
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.");
@@ -751,7 +767,7 @@ export function CoursesProvider({ children }) {
const permanentlyDeleteLessons = useCallback(
(courseId, unitId, { ids }) =>
request(async () => {
const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/lessons/bulk/permanent`, { data: { ids } });
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;
@@ -766,7 +782,7 @@ export function CoursesProvider({ children }) {
const fetchLessonPage = useCallback(
(courseId, unitId, lessonId) =>
request(async () => {
const { data } = await api.get(`${BASE}/${courseId}/units/${unitId}/lessons/${lessonId}/page`);
const { data } = await api.get(`${lessonBase(courseId, unitId, lessonId)}/page`);
const page = data?.data?.data ?? null;
setLessonPage(page);
return data;
@@ -777,7 +793,7 @@ export function CoursesProvider({ children }) {
const saveLessonPage = useCallback(
(courseId, unitId, lessonId, payload) =>
request(async () => {
const { data } = await api.put(`${BASE}/${courseId}/units/${unitId}/lessons/${lessonId}/page`, payload);
const { data } = await api.put(`${lessonBase(courseId, unitId, lessonId)}/page`, payload);
const page = data?.data?.data ?? null;
if (page) {
setLessonPage(page);
@@ -987,7 +1003,7 @@ export function CoursesProvider({ children }) {
(courseId, unitId, quizId) =>
request(async () => {
setCompletions(null);
const { data } = await api.get(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}/completions`);
const { data } = await api.get(`${unitBase(courseId, unitId)}/quiz/${quizId}/completions`);
setCompletions(data?.data ?? null);
return data;
}),
@@ -1109,7 +1125,7 @@ export function CoursesProvider({ children }) {
const fetchLessonFieldValues = useCallback(
(courseId, unitId, field) =>
request(async () => {
const { data } = await api.get(`${BASE}/${courseId}/units/${unitId}/field-values`, { params: { field } });
const { data } = await api.get(`${unitBase(courseId, unitId)}/field-values`, { params: { field } });
const result = data?.data ?? [];
return result;
}),