192 lines
7.7 KiB
TypeScript
192 lines
7.7 KiB
TypeScript
import { useEffect, useState } from "react"
|
|
import { Link, useParams } from "react-router-dom"
|
|
import { BookOpen, Mic, Briefcase, HelpCircle, ArrowLeft, ArrowRight, ChevronRight } from "lucide-react"
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../../components/ui/card"
|
|
import { Button } from "../../components/ui/button"
|
|
import { getCourseCategories } from "../../api/courses.api"
|
|
import type { CourseCategory } from "../../types/course.types"
|
|
|
|
const contentSections = [
|
|
{
|
|
key: "courses",
|
|
pathFn: (categoryId: string | undefined) => `/content/category/${categoryId}/courses`,
|
|
icon: BookOpen,
|
|
title: "Courses",
|
|
description: "Manage course videos and educational content",
|
|
action: "Manage Courses",
|
|
count: 12,
|
|
countLabel: "courses",
|
|
gradient: "from-brand-500/10 via-brand-400/5 to-transparent",
|
|
accentBorder: "group-hover:border-brand-400",
|
|
},
|
|
{
|
|
key: "speaking",
|
|
pathFn: () => "/content/speaking",
|
|
icon: Mic,
|
|
title: "Speaking",
|
|
description: "Manage speaking practice sessions and exercises",
|
|
action: "Manage Speaking",
|
|
count: 8,
|
|
countLabel: "sessions",
|
|
gradient: "from-purple-500/10 via-purple-400/5 to-transparent",
|
|
accentBorder: "group-hover:border-purple-400",
|
|
},
|
|
{
|
|
key: "practices",
|
|
pathFn: () => "/content/practices",
|
|
icon: Briefcase,
|
|
title: "Practice",
|
|
description: "Manage practice details, members, and leadership",
|
|
action: "Manage Practice",
|
|
count: 5,
|
|
countLabel: "practices",
|
|
gradient: "from-indigo-500/10 via-indigo-400/5 to-transparent",
|
|
accentBorder: "group-hover:border-indigo-400",
|
|
},
|
|
{
|
|
key: "questions",
|
|
pathFn: () => "/content/questions",
|
|
icon: HelpCircle,
|
|
title: "Questions",
|
|
description: "Manage questions, quizzes, and assessments",
|
|
action: "Manage Questions",
|
|
count: 34,
|
|
countLabel: "questions",
|
|
gradient: "from-rose-500/10 via-rose-400/5 to-transparent",
|
|
accentBorder: "group-hover:border-rose-400",
|
|
},
|
|
] as const
|
|
|
|
export function ContentOverviewPage() {
|
|
const { categoryId } = useParams<{ categoryId: string }>()
|
|
const [category, setCategory] = useState<CourseCategory | null>(null)
|
|
|
|
useEffect(() => {
|
|
const fetchCategory = async () => {
|
|
try {
|
|
const res = await getCourseCategories()
|
|
const found = res.data.data.categories.find((c) => c.id === Number(categoryId))
|
|
setCategory(found ?? null)
|
|
} catch (err) {
|
|
console.error("Failed to fetch category:", err)
|
|
}
|
|
}
|
|
|
|
if (categoryId) {
|
|
fetchCategory()
|
|
}
|
|
}, [categoryId])
|
|
|
|
return (
|
|
<div className="space-y-8">
|
|
{/* Header & Breadcrumb */}
|
|
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<Link
|
|
to="/content"
|
|
className="grid h-9 w-9 shrink-0 place-items-center rounded-xl border border-grayScale-100 bg-white text-grayScale-400 shadow-sm transition-all duration-200 hover:border-brand-200 hover:bg-brand-50 hover:text-brand-600 hover:shadow-md"
|
|
>
|
|
<ArrowLeft className="h-4 w-4" />
|
|
</Link>
|
|
|
|
<div className="flex items-center gap-1.5 text-sm text-grayScale-400">
|
|
<Link to="/content" className="transition-colors hover:text-brand-500">
|
|
Content
|
|
</Link>
|
|
<ChevronRight className="h-3.5 w-3.5" />
|
|
<span className="font-medium text-grayScale-600">
|
|
{category?.name ?? "Overview"}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<h1 className="text-xl font-bold tracking-tight text-grayScale-700">
|
|
{category?.name ?? "Content Management"}
|
|
</h1>
|
|
</div>
|
|
|
|
{/* Gradient Divider */}
|
|
<div className="relative">
|
|
<div className="absolute inset-0 flex items-center" aria-hidden="true">
|
|
<div className="w-full border-t border-grayScale-100" />
|
|
</div>
|
|
<div className="relative flex justify-center">
|
|
<div
|
|
className="h-1 w-24 rounded-full"
|
|
style={{
|
|
background: "linear-gradient(90deg, #9E2891 0%, #6A1B9A 100%)",
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Cards Grid */}
|
|
<div className="grid gap-6 grid-cols-1 sm:grid-cols-2 lg:grid-cols-4">
|
|
{contentSections.map((section) => {
|
|
const Icon = section.icon
|
|
return (
|
|
<Link
|
|
key={section.key}
|
|
to={section.pathFn(categoryId)}
|
|
className="group"
|
|
>
|
|
<Card
|
|
className={`relative h-full overflow-hidden border border-grayScale-100 bg-white transition-all duration-300 ease-out group-hover:-translate-y-1 group-hover:scale-[1.02] ${section.accentBorder} group-hover:shadow-lg`}
|
|
style={{
|
|
boxShadow: "0 8px 24px rgba(0,0,0,0.06)",
|
|
}}
|
|
>
|
|
{/* Subtle gradient background on icon area */}
|
|
<div
|
|
className={`absolute inset-x-0 top-0 h-28 bg-gradient-to-b ${section.gradient} pointer-events-none`}
|
|
/>
|
|
|
|
<CardHeader className="relative pb-2">
|
|
<div className="mb-4 flex items-start justify-between">
|
|
{/* Icon with gradient ring */}
|
|
<div className="relative">
|
|
<div
|
|
className="grid h-12 w-12 place-items-center rounded-xl bg-white text-brand-600 shadow-sm ring-1 ring-grayScale-100 transition-all duration-300 group-hover:ring-brand-300 group-hover:shadow-md"
|
|
style={{
|
|
background:
|
|
"linear-gradient(135deg, rgba(158,40,145,0.08) 0%, rgba(106,27,154,0.04) 100%)",
|
|
}}
|
|
>
|
|
<Icon className="h-5.5 w-5.5 transition-transform duration-300 group-hover:scale-110" />
|
|
</div>
|
|
{/* Decorative dot */}
|
|
<div className="absolute -right-0.5 -top-0.5 h-2.5 w-2.5 rounded-full border-2 border-white bg-brand-400 opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
|
|
</div>
|
|
|
|
{/* Count Badge */}
|
|
<span className="inline-flex items-center gap-1 rounded-full bg-grayScale-50 px-2.5 py-1 text-xs font-medium text-grayScale-500 ring-1 ring-inset ring-grayScale-100 transition-all duration-300 group-hover:bg-brand-50 group-hover:text-brand-600 group-hover:ring-brand-200">
|
|
{section.count} {section.countLabel}
|
|
</span>
|
|
</div>
|
|
|
|
<CardTitle className="text-[15px] font-semibold text-grayScale-700 transition-colors duration-200 group-hover:text-brand-600">
|
|
{section.title}
|
|
</CardTitle>
|
|
<CardDescription className="mt-1 text-[13px] leading-relaxed text-grayScale-400">
|
|
{section.description}
|
|
</CardDescription>
|
|
</CardHeader>
|
|
|
|
<CardContent className="relative pt-0">
|
|
{/* Thin separator */}
|
|
<div className="mb-3 h-px w-full bg-gradient-to-r from-transparent via-grayScale-100 to-transparent" />
|
|
|
|
<span className="inline-flex items-center gap-1.5 text-sm font-medium text-brand-500 transition-colors duration-200 group-hover:text-brand-600">
|
|
{section.action}
|
|
<ArrowRight className="h-4 w-4 transition-transform duration-300 group-hover:translate-x-1.5" />
|
|
</span>
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|