diff --git a/src/contexts/AdminCoursesContext.jsx b/src/contexts/AdminCoursesContext.jsx index 0db6c4e..583c257 100644 --- a/src/contexts/AdminCoursesContext.jsx +++ b/src/contexts/AdminCoursesContext.jsx @@ -167,6 +167,15 @@ export function CoursesProvider({ children }) { [request], ); + const reorderCourses = useCallback( + (courseIds) => + request(async () => { + await api.put(`${BASE}/order`, { course_ids: courseIds }); + return true; + }), + [request], + ); + const archiveCourse = useCallback( (courseId) => request(async () => { @@ -1284,6 +1293,7 @@ export function CoursesProvider({ children }) { createCourse, createCourseFull, updateCourse, + reorderCourses, archiveCourse, archiveCourses, diff --git a/src/modules/admin/components/courses/CourseTable.jsx b/src/modules/admin/components/courses/CourseTable.jsx index d157e05..be74133 100644 --- a/src/modules/admin/components/courses/CourseTable.jsx +++ b/src/modules/admin/components/courses/CourseTable.jsx @@ -29,7 +29,7 @@ export default function CoursesTable() { const navigate = useNavigate(); - const { courses, attributes, pagination, setPagination, loading, fetchCourses, archiveCourse, archiveCourses, fetchCourseFieldValues } = useCourses(); + const { courses, attributes, pagination, setPagination, loading, fetchCourses, reorderCourses, archiveCourse, archiveCourses, fetchCourseFieldValues } = useCourses(); const handleRefsReady = (refs) => { tableRefsRef.current = refs; @@ -42,14 +42,31 @@ export default function CoursesTable() { sheetName: "Courses", }; - const rowActions = useMemo(() => buildRowActions({ + // Reorder — swaps this row with its neighbor in the currently displayed + // (order_index-sorted) list, then persists the new order_index set. + const handleMove = async (row, direction) => { + const idx = courses.findIndex((c) => c.course_id === row.course_id); + const swapIdx = idx + direction; + if (idx < 0 || swapIdx < 0 || swapIdx >= courses.length) return; + + const reordered = [...courses]; + [reordered[idx], reordered[swapIdx]] = [reordered[swapIdx], reordered[idx]]; + + const ok = await reorderCourses(reordered.map((c) => c.course_id)); + if (ok) fetchCourses({ page: 1, limit: pagination?.limit ?? 10 }); + }; + + const rowActions = buildRowActions({ onViewAssessment: (row) => navigate(`/admin/courses/${row.course_id}/assessment/view`), onAssessment: (row) => navigate(`/admin/courses/${row.course_id}/assessment`), onViewUnits: (row) => navigate(`/admin/courses/${row.course_id}/units`), onView: (row) => navigate(`/admin/courses/${row.course_id}/view`), onEdit: (row) => navigate(`/admin/courses/${row.course_id}/edit`), onArchive: (row) => setArchiveTarget(row), - }), []); + onMoveUp: (row) => handleMove(row, -1), + onMoveDown: (row) => handleMove(row, 1), + courses, + }); const toolbarActions = buildToolbarActions({ fetchCourses, @@ -70,7 +87,7 @@ export default function CoursesTable() { const columns = useMemo( () => buildDataColumns(attributes, rowActions), - [attributes] + [attributes, rowActions] ); const handleArchiveSuccess = () => { diff --git a/src/modules/admin/config/courses/rowActions.config.jsx b/src/modules/admin/config/courses/rowActions.config.jsx index c8bd542..099ac76 100644 --- a/src/modules/admin/config/courses/rowActions.config.jsx +++ b/src/modules/admin/config/courses/rowActions.config.jsx @@ -1,6 +1,6 @@ -import { Eye, Archive, ShelvingUnit, NotebookPen, ClipboardList, PlusCircle } from "lucide-react"; +import { Eye, Archive, ShelvingUnit, NotebookPen, ClipboardList, PlusCircle, ArrowUp, ArrowDown } from "lucide-react"; -export function buildRowActions({ onViewUnits, onView, onEdit, onArchive, onAssessment, onViewAssessment }) { +export function buildRowActions({ onViewUnits, onView, onEdit, onArchive, onAssessment, onViewAssessment, onMoveUp, onMoveDown, courses = [] }) { return [ { key: "view", @@ -8,6 +8,21 @@ export function buildRowActions({ onViewUnits, onView, onEdit, onArchive, onAsse icon: , onClick: (row) => onView(row), }, + { + key: "move-up", + label: "Move Up", + icon: , + onClick: (row) => onMoveUp(row), + separator: true, + disabled: (row) => courses.findIndex((c) => c.course_id === row.course_id) <= 0, + }, + { + key: "move-down", + label: "Move Down", + icon: , + onClick: (row) => onMoveDown(row), + disabled: (row) => courses.findIndex((c) => c.course_id === row.course_id) >= courses.length - 1, + }, { key: "view_units", label: "View Units", diff --git a/src/modules/admin/config/task_list/task/rowActions.config.jsx b/src/modules/admin/config/task_list/task/rowActions.config.jsx index 45c7da1..989e25f 100644 --- a/src/modules/admin/config/task_list/task/rowActions.config.jsx +++ b/src/modules/admin/config/task_list/task/rowActions.config.jsx @@ -8,14 +8,6 @@ export function buildRowActions({ navigate, onArchive, onRestore, onMoveUp, onMo icon: , onClick: (row) => navigate(`${row.task_id}/view`), }, - { - key: "completions", - label: "Completions", - icon: , - onClick: (row) => navigate(`${row.task_id}/completions`), - separator: true, - className: "text-sky-600", - }, { key: "move-up", label: "Move Up", diff --git a/src/modules/admin/config/task_list/task_completion/rowActions.config.jsx b/src/modules/admin/config/task_list/task_completion/rowActions.config.jsx index aead182..2f5f0fd 100644 --- a/src/modules/admin/config/task_list/task_completion/rowActions.config.jsx +++ b/src/modules/admin/config/task_list/task_completion/rowActions.config.jsx @@ -1,13 +1,13 @@ // config/task_completion/rowActions.config.jsx import { Eye, Archive, RotateCcw, ShieldCheck } from "lucide-react"; -export function buildRowActions({ navigate, onArchive, onRestore, onReview, showArchived }) { +export function buildRowActions({ navigate, taskListId, taskId, onArchive, onRestore, onReview, showArchived }) { return [ { key: "view", label: "View", icon: , - onClick: (row) => navigate(`${row.completion_id}/view`), + onClick: (row) => navigate(`/admin/taskList/${taskListId}/tasks/${taskId}/completions/${row.completion_id}/view`), }, { key: "review", diff --git a/src/modules/admin/pages/courses/AddCourse.jsx b/src/modules/admin/pages/courses/AddCourse.jsx index 148d405..cd8faa1 100644 --- a/src/modules/admin/pages/courses/AddCourse.jsx +++ b/src/modules/admin/pages/courses/AddCourse.jsx @@ -35,7 +35,6 @@ const schema = z.object({ title: z.string().min(1, "Title is required."), description: z.string().optional(), course_code: z.string().min(1, "Course Code is required."), - order_index: z.coerce.number().min(0).default(0), level: z.enum(["beginner", "intermediate", "advanced"]).optional(), subscription: z.string().min(1, "Subscription is required.").default("free"), status: z.enum(["draft", "published", "unpublished"]).default("draft"), @@ -173,7 +172,6 @@ export default function AddCourse() { title: "", description: "", course_code: "", - order_index: 0, level: "beginner", subscription: "free", status: "draft", @@ -192,7 +190,6 @@ export default function AddCourse() { const watchedTitle = useWatch({ control, name: "title" }); const watchedDescription = useWatch({ control, name: "description" }); const watchedCourseCode = useWatch({ control, name: "course_code" }); - const watchedOrderIndex = useWatch({ control, name: "order_index" }); const watchedLevel = useWatch({ control, name: "level" }); const watchedSubscr = useWatch({ control, name: "subscription" }); const watchedStatus = useWatch({ control, name: "status" }); @@ -252,7 +249,6 @@ export default function AddCourse() { title: values.title, description: values.description, course_code: values.course_code || null, - order_index: values.order_index, level: values.level || null, subscription: values.subscription, status: values.status, @@ -338,19 +334,12 @@ export default function AddCourse() {