From 46c0c78214060d61d717e5be138d1cce32920977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Ckirukib=E2=80=9D?= <“kirubeljkl679@gmail.com”> Date: Fri, 27 Feb 2026 19:39:58 +0300 Subject: [PATCH] changes --- src/app/AppRoutes.tsx | 4 + .../content-management/AllCoursesPage.tsx | 413 ++++++++++++++++ .../ContentManagementLayout.tsx | 1 + .../ContentOverviewPage.tsx | 160 +++++-- .../CourseFlowBuilderPage.tsx | 447 ++++++++++++++++++ src/pages/content-management/CoursesPage.tsx | 30 ++ 6 files changed, 1003 insertions(+), 52 deletions(-) create mode 100644 src/pages/content-management/AllCoursesPage.tsx create mode 100644 src/pages/content-management/CourseFlowBuilderPage.tsx diff --git a/src/app/AppRoutes.tsx b/src/app/AppRoutes.tsx index 730eed3..a93f80f 100644 --- a/src/app/AppRoutes.tsx +++ b/src/app/AppRoutes.tsx @@ -4,6 +4,8 @@ import { DashboardPage } from "../pages/DashboardPage" import { AnalyticsPage } from "../pages/analytics/AnalyticsPage" import { ContentManagementLayout } from "../pages/content-management/ContentManagementLayout" import { CourseCategoryPage } from "../pages/content-management/CourseCategoryPage" +import { AllCoursesPage } from "../pages/content-management/AllCoursesPage" +import { CourseFlowBuilderPage } from "../pages/content-management/CourseFlowBuilderPage" import { ContentOverviewPage } from "../pages/content-management/ContentOverviewPage" import { CoursesPage } from "../pages/content-management/CoursesPage" import { PracticeQuestionsPage } from "../pages/content-management/PracticeQuestionsPage" @@ -62,6 +64,8 @@ export function AppRoutes() { }> } /> + } /> + } /> } /> } /> {/* Course → Sub-course → Video/Practice */} diff --git a/src/pages/content-management/AllCoursesPage.tsx b/src/pages/content-management/AllCoursesPage.tsx new file mode 100644 index 0000000..3c2cb6a --- /dev/null +++ b/src/pages/content-management/AllCoursesPage.tsx @@ -0,0 +1,413 @@ +import { useEffect, useState } from "react" +import { useNavigate } from "react-router-dom" +import { Search, Plus, RefreshCw } 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 } from "../../api/courses.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" + +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 [createTitle, setCreateTitle] = useState("") + const [createDescription, setCreateDescription] = useState("") + const [createThumbnail, setCreateThumbnail] = useState(null) + const [createVideo, setCreateVideo] = useState(null) + const [creating, setCreating] = useState(false) + + 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 courses") + } 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 handleCreateCourse = async () => { + if (!createCategoryId || !createTitle.trim() || !createDescription.trim()) { + toast.error("Missing fields", { + description: "Category, title, and description are required.", + }) + return + } + + setCreating(true) + try { + await createCourse({ + category_id: Number(createCategoryId), + title: createTitle.trim(), + description: createDescription.trim(), + }) + + toast.success("Course created", { + description: `"${createTitle.trim()}" has been created.`, + }) + + setCreateOpen(false) + setCreateCategoryId("") + setCreateTitle("") + setCreateDescription("") + setCreateThumbnail(null) + setCreateVideo(null) + await fetchAllCourses() + } catch (err: any) { + console.error("Failed to create course:", err) + toast.error("Failed to create course", { + description: err?.response?.data?.message || "Please try again.", + }) + } finally { + setCreating(false) + } + } + + if (loading) { + return ( +
+
+ +
+

Loading all courses…

+
+ ) + } + + if (error) { + return ( +
+
+
+ +
+

{error}

+
+
+ ) + } + + return ( +
+ {/* Header */} +
+
+

All Courses

+

+ View and manage courses across all categories. +

+
+ +
+ + + + + Course Management + + + + {/* Search / Filters */} +
+
+ + setSearch(e.target.value)} + 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 + + + + + {filteredCourses.map((course, index) => ( + + navigate( + `/content/category/${course.category_id}/courses/${course.id}/sub-courses`, + ) + } + > + +
+ {course.title} +
+ {course.description && ( +
+ {course.description} +
+ )} +
+ + {course.category_name} + + + + {course.is_active ? "Active" : "Inactive"} + + + + + +
+ ))} +
+
+
+ ) : ( +
+
+ +
+

No courses found

+

+ Try adjusting your search or category filter, or create a new course. +

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