import { useEffect, useState } from "react" import { useNavigate } from "react-router-dom" import { Search, Plus, RefreshCw, Edit2, ToggleLeft, ToggleRight, BookOpen, ChevronDown, ChevronLeft, ChevronRight } from "lucide-react" import { Card, CardContent, CardHeader, CardTitle } from "../../components/ui/card" import { Button } from "../../components/ui/button" import { Input } from "../../components/ui/input" import { Select } from "../../components/ui/select" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "../../components/ui/table" import { Badge } from "../../components/ui/badge" import { FileUpload } from "../../components/ui/file-upload" import { getCourseCategories, getCoursesByCategory, createCourse, updateCourseStatus, updateCourse, updateCourseThumbnail } from "../../api/courses.api" import { uploadImageFile } from "../../api/files.api" import type { Course, CourseCategory } from "../../types/course.types" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "../../components/ui/dialog" import { Textarea } from "../../components/ui/textarea" import { toast } from "sonner" import { cn } from "../../lib/utils" import { SpinnerIcon } from "../../components/ui/spinner-icon" type CourseWithCategory = Course & { category_name: string } export function AllCoursesPage() { const navigate = useNavigate() const [courses, setCourses] = useState([]) const [categories, setCategories] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [search, setSearch] = useState("") const [categoryFilter, setCategoryFilter] = useState<"all" | string>("all") const [createOpen, setCreateOpen] = useState(false) const [createCategoryId, setCreateCategoryId] = useState("") const [createSubCategoryId, setCreateSubCategoryId] = useState("") const [createTitle, setCreateTitle] = useState("") const [createDescription, setCreateDescription] = useState("") const [createThumbnail, setCreateThumbnail] = useState(null) const [createVideo, setCreateVideo] = useState(null) const [creating, setCreating] = useState(false) const [togglingId, setTogglingId] = useState(null) const [editOpen, setEditOpen] = useState(false) const [courseToEdit, setCourseToEdit] = useState(null) const [editTitle, setEditTitle] = useState("") const [editDescription, setEditDescription] = useState("") const [updating, setUpdating] = useState(false) const [page, setPage] = useState(1) const [pageSize, setPageSize] = useState(10) const fetchAllCourses = async () => { setLoading(true) setError(null) try { const categoriesRes = await getCourseCategories() const cats = categoriesRes.data.data.categories ?? [] setCategories(cats) const allCourses: CourseWithCategory[] = [] for (const cat of cats) { const res = await getCoursesByCategory(cat.id) const catCourses = res.data.data.courses ?? [] allCourses.push( ...catCourses.map((c) => ({ ...c, category_name: cat.name, })), ) } setCourses(allCourses) } catch (err) { console.error("Failed to load courses:", err) setError("Failed to load sub-categories") } finally { setLoading(false) } } useEffect(() => { fetchAllCourses() }, []) const filteredCourses = courses.filter((course) => { if (categoryFilter !== "all" && String(course.category_id) !== categoryFilter) { return false } if (search.trim()) { const q = search.toLowerCase() const haystack = `${course.title} ${course.description} ${course.category_name}`.toLowerCase() if (!haystack.includes(q)) return false } return true }) 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 } const handleCreateCourse = async () => { const effectiveCategoryId = createSubCategoryId || createCategoryId if (!effectiveCategoryId || !createTitle.trim() || !createDescription.trim()) { toast.error("Missing fields", { description: "Category (or subcategory), title, and description are required.", }) return } setCreating(true) try { const createdRes = await createCourse({ category_id: Number(effectiveCategoryId), title: createTitle.trim(), description: createDescription.trim(), }) const createdIdRaw = (createdRes.data?.data as any)?.id ?? (createdRes.data?.data as any)?.course?.id ?? (createdRes.data?.data as any)?.data?.id const createdId = Number(createdIdRaw) if (createThumbnail && Number.isFinite(createdId)) { const uploadRes = await uploadImageFile(createThumbnail) const thumbnailUrl = uploadRes.data?.data?.url?.trim() if (thumbnailUrl) { await updateCourseThumbnail(createdId, thumbnailUrl) } } toast.success("Sub-category created", { description: `"${createTitle.trim()}" has been created.`, }) setCreateOpen(false) setCreateCategoryId("") setCreateSubCategoryId("") setCreateTitle("") setCreateDescription("") setCreateThumbnail(null) setCreateVideo(null) await fetchAllCourses() } catch (err: any) { console.error("Failed to create course:", err) toast.error("Failed to create sub-category", { description: err?.response?.data?.message || "Please try again.", }) } finally { setCreating(false) } } const handleToggleStatus = async (course: CourseWithCategory) => { setTogglingId(course.id) try { await updateCourseStatus(course.id, !course.is_active) await fetchAllCourses() } catch (err) { console.error("Failed to update course status:", err) toast.error("Failed to update sub-category status") } finally { setTogglingId(null) } } const openEditDialog = (course: CourseWithCategory) => { setCourseToEdit(course) setEditTitle(course.title) setEditDescription(course.description || "") setEditOpen(true) } const handleUpdateCourse = async () => { if (!courseToEdit) return if (!editTitle.trim() || !editDescription.trim()) { toast.error("Missing fields", { description: "Title and description are required.", }) return } setUpdating(true) try { await updateCourse(courseToEdit.id, { title: editTitle.trim(), description: editDescription.trim(), }) toast.success("Sub-category updated") setEditOpen(false) setCourseToEdit(null) await fetchAllCourses() } catch (err: any) { console.error("Failed to update course:", err) toast.error("Failed to update sub-category", { description: err?.response?.data?.message || "Please try again.", }) } finally { setUpdating(false) } } if (loading) { return (

Loading all sub-categories…

) } if (error) { return (

{error}

) } return (
{/* Header */}

All Sub-categories

View and manage sub-categories across all categories.

Sub-category Management {/* Search / Filters */}
{ setSearch(e.target.value) setPage(1) }} className="pl-10 transition-colors focus:border-brand-300 focus:ring-brand-200" />
Showing {filteredCourses.length} of {courses.length} courses
{/* Courses Table */} {filteredCourses.length > 0 ? (
Course Category Status Actions {paginatedCourses.map((course) => ( navigate( `/content/category/${course.category_id}/courses/${course.id}/sub-modules`, ) } >
{course.title}
{course.description && (
{course.description}
)}
{course.category_name} {course.is_active ? "Active" : "Inactive"}
))}
Showing {startEntry}-{endEntry} of {totalCount} entries Rows per page
{getPageNumbers().map((n, idx) => typeof n === "string" ? ( ... ) : ( ), )}
) : (

No sub-categories found

Try adjusting your search or category filter, or create a new sub-category.

)}
{/* Create course dialog */} Create sub-category Choose a category, add basic details, and optionally attach a thumbnail and intro video.
setCreateTitle(e.target.value)} />