import { useEffect, useMemo, useState } from "react" import { Link, useParams, useNavigate } from "react-router-dom" import { Plus, ArrowLeft, ToggleLeft, ToggleRight, X, Trash2, Edit, AlertCircle, Star, MessageSquare, ChevronDown, ChevronLeft, ChevronRight, Search } from "lucide-react" import practiceSrc from "../../assets/Practice.svg" import spinnerSrc from "../../assets/Circular-indeterminate progress indicator.svg" import { Card, CardContent, CardHeader, CardTitle } from "../../components/ui/card" import alertSrc from "../../assets/Alert.svg" import { Button } from "../../components/ui/button" import { Badge } from "../../components/ui/badge" import { Input } from "../../components/ui/input" import { FileUpload } from "../../components/ui/file-upload" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "../../components/ui/table" import { getCoursesByCategory, getCourseCategories, createCourse, deleteCourse, updateCourseStatus, updateCourse, updateCourseThumbnail, getRatings, } from "../../api/courses.api" import { uploadImageFile } from "../../api/files.api" import type { Course, CourseCategory, Rating } from "../../types/course.types" import { cn } from "../../lib/utils" import { SpinnerIcon } from "../../components/ui/spinner-icon" export function CoursesPage() { const { categoryId } = useParams<{ categoryId: string }>() const navigate = useNavigate() const [courses, setCourses] = useState([]) const [searchQuery, setSearchQuery] = useState("") const [category, setCategory] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [showModal, setShowModal] = useState(false) const [title, setTitle] = useState("") const [description, setDescription] = useState("") const [saving, setSaving] = useState(false) const [saveError, setSaveError] = useState(null) const [showDeleteModal, setShowDeleteModal] = useState(false) const [courseToDelete, setCourseToDelete] = useState(null) const [deleting, setDeleting] = useState(false) const [togglingId, setTogglingId] = useState(null) const [showEditModal, setShowEditModal] = useState(false) const [courseToEdit, setCourseToEdit] = useState(null) const [editTitle, setEditTitle] = useState("") const [editDescription, setEditDescription] = useState("") const [editThumbnail, setEditThumbnail] = useState("") const [editThumbnailFile, setEditThumbnailFile] = useState(null) const [updating, setUpdating] = useState(false) const [updateError, setUpdateError] = useState(null) const [showRatingsModal, setShowRatingsModal] = useState(false) const [ratingsCourseId, setRatingsCourseId] = useState(null) const [courseRatings, setCourseRatings] = useState([]) const [courseRatingsLoading, setCourseRatingsLoading] = useState(false) const [page, setPage] = useState(1) const [pageSize, setPageSize] = useState(10) const fetchCourses = async () => { if (!categoryId) return try { const coursesRes = await getCoursesByCategory(Number(categoryId)) console.log("Courses response:", coursesRes.data.data.courses) setCourses(coursesRes.data.data.courses ?? []) } catch (err) { console.error("Failed to fetch courses:", err) } } useEffect(() => { const fetchData = async () => { if (!categoryId) return try { const [coursesRes, categoriesRes] = await Promise.all([ getCoursesByCategory(Number(categoryId)), getCourseCategories(), ]) setCourses(coursesRes.data.data.courses ?? []) const foundCategory = categoriesRes.data.data.categories.find( (c) => c.id === Number(categoryId) ) setCategory(foundCategory ?? null) } catch (err) { console.error("Failed to fetch courses:", err) setError("Failed to load sub-categories") } finally { setLoading(false) } } fetchData() }, [categoryId]) useEffect(() => { setPage(1) }, [categoryId, searchQuery]) const handleOpenModal = () => { setTitle("") setDescription("") setSaveError(null) setShowModal(true) } const handleCloseModal = () => { setShowModal(false) setTitle("") setDescription("") setSaveError(null) } const handleSave = async () => { if (!title.trim()) { setSaveError("Title is required") return } if (!description.trim()) { setSaveError("Description is required") return } setSaving(true) setSaveError(null) try { await createCourse({ category_id: Number(categoryId), title: title.trim(), description: description.trim(), }) handleCloseModal() await fetchCourses() } catch (err: any) { console.error("Failed to create course:", err) setSaveError(err.response?.data?.message || "Failed to create sub-category") } finally { setSaving(false) } } const handleDeleteClick = (course: Course) => { setCourseToDelete(course) setShowDeleteModal(true) } const handleConfirmDelete = async () => { if (!courseToDelete) return setDeleting(true) try { await deleteCourse(courseToDelete.id) setShowDeleteModal(false) setCourseToDelete(null) await fetchCourses() } catch (err) { console.error("Failed to delete course:", err) } finally { setDeleting(false) } } const handleToggleStatus = async (course: Course) => { setTogglingId(course.id) try { await updateCourseStatus(course.id, !course.is_active) await fetchCourses() } catch (err) { console.error("Failed to update course status:", err) } finally { setTogglingId(null) } } const handleEditClick = (course: Course) => { setCourseToEdit(course) setEditTitle(course.title || "") setEditDescription(course.description || "") setEditThumbnail(course.thumbnail || "") setEditThumbnailFile(null) setUpdateError(null) setShowEditModal(true) } const handleCloseEditModal = () => { setShowEditModal(false) setCourseToEdit(null) setEditTitle("") setEditDescription("") setEditThumbnail("") setEditThumbnailFile(null) setUpdateError(null) } const handleUpdate = async () => { if (!courseToEdit) return if (!editTitle.trim()) { setUpdateError("Title is required") return } if (!editDescription.trim()) { setUpdateError("Description is required") return } setUpdating(true) setUpdateError(null) try { await updateCourse(courseToEdit.id, { title: editTitle.trim(), description: editDescription.trim(), is_active: courseToEdit.is_active, }) const thumbnailUrl = editThumbnailFile ? (await uploadImageFile(editThumbnailFile)).data?.data?.url?.trim() : editThumbnail.trim() || "" if (thumbnailUrl) { await updateCourseThumbnail(courseToEdit.id, thumbnailUrl) } handleCloseEditModal() await fetchCourses() } catch (err: any) { console.error("Failed to update course:", err) setUpdateError(err.response?.data?.message || "Failed to update sub-category") } finally { setUpdating(false) } } const handleCourseClick = (courseId: number) => { navigate(`/content/category/${categoryId}/courses/${courseId}/sub-modules`) } const handleViewRatings = async (courseId: number) => { setRatingsCourseId(courseId) setShowRatingsModal(true) setCourseRatingsLoading(true) try { const res = await getRatings({ target_type: "course", target_id: courseId, limit: 10 }) setCourseRatings(res.data.data ?? []) } catch (err) { console.error("Failed to fetch ratings:", err) } finally { setCourseRatingsLoading(false) } } const filteredCourses = useMemo(() => { const q = searchQuery.trim().toLowerCase() if (!q) return courses return courses.filter((course) => { const haystack = `${course.title} ${course.description ?? ""} ${course.id}`.toLowerCase() return haystack.includes(q) }) }, [courses, searchQuery]) if (loading) { return (
) } if (error) { return (

{error}

) } const totalCount = filteredCourses.length const totalPages = Math.max(1, Math.ceil(totalCount / pageSize)) const safePage = Math.min(page, totalPages) const paginatedCourses = filteredCourses.slice((safePage - 1) * pageSize, safePage * pageSize) const startEntry = totalCount === 0 ? 0 : (safePage - 1) * pageSize + 1 const endEntry = Math.min(safePage * pageSize, totalCount) const getPageNumbers = () => { const pages: (number | string)[] = [] if (totalPages <= 7) { for (let i = 1; i <= totalPages; i++) pages.push(i) } else { pages.push(1, 2, 3) if (safePage > 4) pages.push("...") if (safePage > 3 && safePage < totalPages - 2) pages.push(safePage) if (safePage < totalPages - 3) pages.push("...") pages.push(totalPages) } return pages } return (
{/* Header */}

{category?.name} Sub-categories

{courses.length} sub-categories available

{/* Course table or empty state */}
Sub-category Management
setSearchQuery(e.target.value)} placeholder="Search sub-categories..." className="pl-9" />
{courses.length === 0 ? (

No sub-categories yet

No sub-categories found in this category.

) : filteredCourses.length === 0 ? (

No matching sub-categories

Try a different search term.

) : (
Sub-category Status Actions {paginatedCourses.map((course) => ( handleCourseClick(course.id)} >
{course.title}
{course.description && (
{course.description}
)}
{course.is_active ? "Active" : "Inactive"}
))}
Showing {startEntry}-{endEntry} of {totalCount} entries Rows per page
{getPageNumbers().map((n, idx) => typeof n === "string" ? ( ... ) : ( ), )}
)}
{/* Add Course Modal */} {showModal && (

Add New Sub-category

{saveError && (
{saveError}
)}
setTitle(e.target.value)} />