2026-09-09 22:16:47 +07:00
|
|
|
import { useState, useEffect } from 'react';
|
2026-09-08 20:39:30 +07:00
|
|
|
import { useParams, useNavigate } from 'react-router-dom';
|
2026-09-03 21:49:25 +07:00
|
|
|
import { Card } from '../../components/ui/card';
|
|
|
|
|
import { Button } from '../../components/ui/button';
|
|
|
|
|
import { Badge } from '../../components/ui/badge';
|
|
|
|
|
import { Label } from '../../components/ui/label';
|
|
|
|
|
import { Input } from '../../components/ui/input';
|
|
|
|
|
import { Textarea } from '../../components/ui/textarea';
|
2026-09-08 20:39:30 +07:00
|
|
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../../components/ui/tabs';
|
|
|
|
|
import { Separator } from '../../components/ui/separator';
|
|
|
|
|
import {
|
|
|
|
|
HiOutlinePlus,
|
|
|
|
|
HiOutlinePencil,
|
|
|
|
|
HiOutlineTrash,
|
|
|
|
|
HiOutlineEye,
|
|
|
|
|
HiOutlineChevronRight,
|
|
|
|
|
HiOutlineBookOpen,
|
|
|
|
|
HiOutlineVideoCamera,
|
|
|
|
|
HiOutlineDocumentText,
|
|
|
|
|
HiOutlineQuestionMarkCircle,
|
|
|
|
|
HiOutlineCodeBracket,
|
|
|
|
|
HiOutlineArrowUpTray,
|
|
|
|
|
HiOutlineFunnel,
|
|
|
|
|
} from 'react-icons/hi2';
|
2026-09-09 22:16:47 +07:00
|
|
|
import courseService from '@/services/courseService';
|
|
|
|
|
import lessonService from '@/services/lessonService';
|
|
|
|
|
import sectionService from '@/services/sectionService';
|
2026-09-08 20:39:30 +07:00
|
|
|
|
|
|
|
|
const typeConfig = {
|
|
|
|
|
video: { label: 'Video', icon: HiOutlineVideoCamera, color: 'bg-blue-100 text-blue-700' },
|
|
|
|
|
text: { label: 'Text', icon: HiOutlineDocumentText, color: 'bg-purple-100 text-purple-700' },
|
|
|
|
|
quiz: { label: 'Quiz', icon: HiOutlineQuestionMarkCircle, color: 'bg-orange-100 text-orange-700' },
|
|
|
|
|
embed: { label: 'Embed', icon: HiOutlineCodeBracket, color: 'bg-gray-100 text-gray-700' },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const statusStyles = {
|
|
|
|
|
Published: 'bg-app-success text-app-success-text',
|
|
|
|
|
Draft: 'bg-app-neutral-badge text-app-neutral-badge-text',
|
|
|
|
|
};
|
|
|
|
|
|
2026-09-09 22:16:47 +07:00
|
|
|
function formatDuration(seconds) {
|
|
|
|
|
if (!seconds) return '00:00';
|
|
|
|
|
const m = Math.floor(seconds / 60);
|
|
|
|
|
const s = seconds % 60;
|
|
|
|
|
return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function mapCourse(c) {
|
|
|
|
|
return {
|
|
|
|
|
id: c.id,
|
|
|
|
|
code: c.courseCode,
|
|
|
|
|
title: c.title,
|
|
|
|
|
description: c.description,
|
|
|
|
|
status: c.isPublished ? 'Active' : 'Inactive',
|
|
|
|
|
lastUpdate: c.updatedAt,
|
|
|
|
|
image: c.image,
|
|
|
|
|
isPublished: c.isPublished,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-19 21:15:42 +07:00
|
|
|
function CourseDetail() {
|
|
|
|
|
const { courseId } = useParams();
|
2026-09-08 20:39:30 +07:00
|
|
|
const navigate = useNavigate();
|
2026-09-09 22:16:47 +07:00
|
|
|
const [course, setCourse] = useState(null);
|
|
|
|
|
const [lessons, setLessons] = useState([]);
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
const [error, setError] = useState(null);
|
2026-09-08 20:39:30 +07:00
|
|
|
const [activeTab, setActiveTab] = useState('overview');
|
2026-08-19 21:15:42 +07:00
|
|
|
|
2026-09-09 22:16:47 +07:00
|
|
|
const handleTogglePublish = async () => {
|
|
|
|
|
try {
|
|
|
|
|
await courseService.update(courseId, {
|
|
|
|
|
courseCode: course.code,
|
|
|
|
|
title: course.title,
|
|
|
|
|
description: course.description,
|
|
|
|
|
image: course.image,
|
|
|
|
|
isPublished: !course.isPublished,
|
|
|
|
|
});
|
|
|
|
|
setCourse((prev) => ({ ...prev, isPublished: !prev.isPublished, status: prev.isPublished ? 'Inactive' : 'Active' }));
|
|
|
|
|
} catch (err) {
|
|
|
|
|
alert(err.message || 'Failed to update course');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleDeleteLesson = async (lessonId) => {
|
|
|
|
|
if (!window.confirm('Are you sure you want to delete this lesson?')) return;
|
|
|
|
|
try {
|
|
|
|
|
await lessonService.delete(lessonId);
|
|
|
|
|
setLessons((prev) => prev.filter((l) => l.id !== lessonId));
|
|
|
|
|
} catch (err) {
|
|
|
|
|
alert(err.message || 'Failed to delete lesson');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const fetchData = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const courseData = await courseService.getById(courseId);
|
|
|
|
|
setCourse(mapCourse(courseData));
|
|
|
|
|
|
|
|
|
|
const allLessons = await lessonService.getAll();
|
|
|
|
|
const courseLessons = allLessons.filter((l) => l.courseId === Number(courseId));
|
|
|
|
|
|
|
|
|
|
const lessonsWithSections = await Promise.all(
|
|
|
|
|
courseLessons.map(async (lesson) => {
|
|
|
|
|
const sections = await sectionService.getByLessonId(lesson.id);
|
|
|
|
|
const firstSection = sections[0];
|
|
|
|
|
return {
|
|
|
|
|
id: lesson.id,
|
|
|
|
|
title: lesson.title,
|
|
|
|
|
position: lesson.position,
|
|
|
|
|
type: firstSection?.type || 'text',
|
|
|
|
|
duration: firstSection?.type === 'video'
|
|
|
|
|
? formatDuration(firstSection.durationSeconds)
|
|
|
|
|
: firstSection?.type === 'text'
|
|
|
|
|
? `~${Math.max(1, Math.ceil((firstSection.content || '').split(/\s+/).length / 200))} min read`
|
|
|
|
|
: firstSection?.type === 'quiz'
|
|
|
|
|
? `${firstSection.durationSeconds ? Math.floor(firstSection.durationSeconds / 60) : 10}:00`
|
|
|
|
|
: firstSection?.type === 'embed'
|
|
|
|
|
? 'iFrame'
|
|
|
|
|
: '00:00',
|
|
|
|
|
status: lesson.isPublished ? 'Published' : 'Draft',
|
|
|
|
|
isFreePreview: firstSection?.isFreePreview || false,
|
|
|
|
|
updatedAt: lesson.updatedAt,
|
|
|
|
|
};
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
setLessons(lessonsWithSections);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
setError(err.message || 'Failed to load course');
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
fetchData();
|
|
|
|
|
}, [courseId]);
|
|
|
|
|
|
|
|
|
|
if (loading) {
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<div className="flex items-center justify-center py-20 text-app-text-muted">Loading course...</div>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (error || !course) {
|
2026-09-08 20:39:30 +07:00
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<div className="flex flex-col items-center justify-center py-20 text-app-text-muted">
|
|
|
|
|
<HiOutlineBookOpen className="w-16 h-16 mb-4 opacity-30" />
|
2026-09-09 22:16:47 +07:00
|
|
|
<p className="text-lg font-medium">{error || 'Course not found'}</p>
|
2026-09-08 20:39:30 +07:00
|
|
|
<Button
|
|
|
|
|
variant="link"
|
|
|
|
|
className="mt-2 text-app-primary"
|
|
|
|
|
onClick={() => navigate('/courses')}
|
|
|
|
|
>
|
|
|
|
|
Back to Courses
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
);
|
2026-08-19 21:15:42 +07:00
|
|
|
}
|
2026-09-08 20:39:30 +07:00
|
|
|
|
|
|
|
|
const publishedCount = lessons.filter((l) => l.status === 'Published').length;
|
|
|
|
|
const draftCount = lessons.filter((l) => l.status === 'Draft').length;
|
|
|
|
|
|
2026-08-19 21:15:42 +07:00
|
|
|
return (
|
|
|
|
|
<>
|
2026-09-08 20:39:30 +07:00
|
|
|
|
|
|
|
|
{/* Breadcrumb */}
|
|
|
|
|
<div className="flex items-center gap-2 text-sm text-app-text-muted mb-4">
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => navigate('/courses')}
|
|
|
|
|
className="hover:text-app-primary transition-colors flex items-center gap-1"
|
|
|
|
|
>
|
|
|
|
|
<HiOutlineBookOpen className="w-4 h-4" />
|
|
|
|
|
Courses
|
|
|
|
|
</button>
|
|
|
|
|
<HiOutlineChevronRight className="w-3.5 h-3.5" />
|
|
|
|
|
<span className="font-medium text-app-text">{course.title}</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Header */}
|
|
|
|
|
<div className="flex flex-row items-center justify-between mb-6">
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
<h2 className="text-xl font-bold text-app-text">{course.title}</h2>
|
|
|
|
|
<Badge
|
|
|
|
|
className={`rounded-[6px] text-xs font-medium ${
|
|
|
|
|
course.status === 'Active'
|
|
|
|
|
? 'bg-app-success text-app-success-text'
|
|
|
|
|
: 'bg-app-neutral-badge text-app-neutral-badge-text'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{course.status}
|
|
|
|
|
</Badge>
|
|
|
|
|
</div>
|
2026-09-03 21:49:25 +07:00
|
|
|
<div className="flex flex-row items-center gap-2">
|
|
|
|
|
<Button
|
2026-09-08 20:39:30 +07:00
|
|
|
variant="outline"
|
2026-09-03 21:49:25 +07:00
|
|
|
className="rounded-xl bg-app-surface-muted text-app-text hover:bg-app-interactive-hover"
|
2026-09-22 22:37:36 +07:00
|
|
|
onClick={() => navigate(`/courses/edit/${courseId}`)}
|
2026-09-09 22:16:47 +07:00
|
|
|
>
|
|
|
|
|
<HiOutlinePencil className="w-4 h-4 mr-2" />
|
|
|
|
|
Edit
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
className="rounded-xl bg-app-surface-muted text-app-text hover:bg-app-interactive-hover"
|
|
|
|
|
onClick={handleTogglePublish}
|
2026-09-03 21:49:25 +07:00
|
|
|
>
|
2026-09-08 20:39:30 +07:00
|
|
|
<HiOutlineArrowUpTray className="w-4 h-4 mr-2" />
|
2026-09-09 22:16:47 +07:00
|
|
|
{course.isPublished ? 'Inactivate' : 'Activate'}
|
2026-08-19 21:15:42 +07:00
|
|
|
</Button>
|
2026-09-09 22:16:47 +07:00
|
|
|
<Button
|
|
|
|
|
className="rounded-xl bg-app-primary text-white hover:bg-app-primary-dark"
|
|
|
|
|
onClick={handleTogglePublish}
|
|
|
|
|
>
|
|
|
|
|
{course.isPublished ? 'Unpublish' : 'Publish'}
|
2026-09-03 21:49:25 +07:00
|
|
|
</Button>
|
2026-08-19 21:15:42 +07:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-09-08 20:39:30 +07:00
|
|
|
|
|
|
|
|
<Separator className="mb-6" />
|
|
|
|
|
|
|
|
|
|
{/* Tabs */}
|
|
|
|
|
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
|
|
|
|
<TabsList className="mb-6">
|
|
|
|
|
<TabsTrigger value="overview" className="gap-2">
|
|
|
|
|
<HiOutlineBookOpen className="w-4 h-4" />
|
|
|
|
|
Overview
|
|
|
|
|
</TabsTrigger>
|
|
|
|
|
<TabsTrigger value="lessons" className="gap-2">
|
|
|
|
|
<HiOutlineDocumentText className="w-4 h-4" />
|
|
|
|
|
Lessons
|
|
|
|
|
<Badge className="ml-1 rounded-full bg-app-primary-soft text-app-primary text-[10px] px-1.5 py-0">
|
|
|
|
|
{lessons.length}
|
|
|
|
|
</Badge>
|
|
|
|
|
</TabsTrigger>
|
|
|
|
|
</TabsList>
|
|
|
|
|
|
|
|
|
|
{/* Overview Tab */}
|
|
|
|
|
<TabsContent value="overview">
|
|
|
|
|
<Card
|
|
|
|
|
className="w-full max-w-2xl overflow-hidden rounded-xl border border-app-border bg-white"
|
|
|
|
|
style={{ boxShadow: 'rgba(0,0,0,0.03) 0px 4px 24px' }}
|
|
|
|
|
>
|
|
|
|
|
<div className="p-5 border-b border-app-border">
|
|
|
|
|
<h4 className="font-semibold text-app-text">Course Details</h4>
|
2026-09-03 21:49:25 +07:00
|
|
|
</div>
|
2026-09-08 20:39:30 +07:00
|
|
|
<div className="px-5 pb-5 pt-4">
|
|
|
|
|
<div className="mb-4 w-full max-w-sm">
|
|
|
|
|
<Label className="mb-1.5 block text-app-text text-xs font-semibold uppercase tracking-wider">
|
|
|
|
|
Course Code
|
|
|
|
|
</Label>
|
|
|
|
|
<Input value={course.code} readOnly className="bg-app-surface-muted" />
|
|
|
|
|
</div>
|
|
|
|
|
<div className="mb-4">
|
|
|
|
|
<Label className="mb-1.5 block text-app-text text-xs font-semibold uppercase tracking-wider">
|
|
|
|
|
Status
|
|
|
|
|
</Label>
|
|
|
|
|
<Badge
|
|
|
|
|
className={`rounded-[6px] text-xs font-medium ${
|
|
|
|
|
course.status === 'Active'
|
|
|
|
|
? 'bg-app-success text-app-success-text'
|
|
|
|
|
: 'bg-app-neutral-badge text-app-neutral-badge-text'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{course.status}
|
|
|
|
|
</Badge>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="mb-4 w-full max-w-sm">
|
|
|
|
|
<Label className="mb-1.5 block text-app-text text-xs font-semibold uppercase tracking-wider">
|
|
|
|
|
Description
|
|
|
|
|
</Label>
|
|
|
|
|
<Textarea
|
|
|
|
|
className="max-h-40 bg-app-surface-muted"
|
|
|
|
|
placeholder="Course description..."
|
|
|
|
|
rows={4}
|
|
|
|
|
value={course.description}
|
|
|
|
|
readOnly
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center gap-6 text-sm text-app-text-muted">
|
|
|
|
|
<span>
|
|
|
|
|
Last updated:{' '}
|
2026-09-09 22:16:47 +07:00
|
|
|
<span className="font-medium text-app-text">
|
|
|
|
|
{course.lastUpdate ? new Date(course.lastUpdate).toLocaleDateString('en-GB') : '-'}
|
|
|
|
|
</span>
|
2026-09-08 20:39:30 +07:00
|
|
|
</span>
|
|
|
|
|
<span>
|
|
|
|
|
Total lessons:{' '}
|
|
|
|
|
<span className="font-medium text-app-text">{lessons.length}</span>
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Card>
|
|
|
|
|
</TabsContent>
|
|
|
|
|
|
|
|
|
|
{/* Lessons Tab */}
|
|
|
|
|
<TabsContent value="lessons">
|
|
|
|
|
<div className="flex flex-col gap-4">
|
|
|
|
|
{/* Toolbar */}
|
|
|
|
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
|
|
|
<div>
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<h3 className="text-lg font-semibold text-app-text">Lessons</h3>
|
|
|
|
|
<Badge className="rounded-full bg-app-surface-muted text-app-text-muted text-xs">
|
|
|
|
|
{lessons.length} items
|
|
|
|
|
</Badge>
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-sm text-app-text-muted mt-0.5">
|
|
|
|
|
{publishedCount} published, {draftCount} drafts
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
size="sm"
|
|
|
|
|
className="rounded-xl bg-app-surface-muted text-app-text hover:bg-app-interactive-hover"
|
|
|
|
|
>
|
|
|
|
|
<HiOutlineFunnel className="w-4 h-4 mr-1.5" />
|
|
|
|
|
Filter
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
className="rounded-xl bg-app-primary text-white hover:bg-app-primary-dark shadow-sm"
|
|
|
|
|
onClick={() => navigate(`/courses/${courseId}/lessons/new`)}
|
|
|
|
|
>
|
|
|
|
|
<HiOutlinePlus className="w-4 h-4 mr-1.5" />
|
|
|
|
|
Add Lesson
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Lessons Table */}
|
|
|
|
|
{lessons.length === 0 ? (
|
|
|
|
|
<Card
|
|
|
|
|
className="rounded-xl border border-app-border bg-white p-12 flex flex-col items-center justify-center text-center"
|
|
|
|
|
style={{ boxShadow: 'rgba(0,0,0,0.03) 0px 4px 24px' }}
|
|
|
|
|
>
|
|
|
|
|
<div className="w-16 h-16 rounded-2xl bg-app-surface-muted flex items-center justify-center mb-4">
|
|
|
|
|
<HiOutlineBookOpen className="w-8 h-8 text-app-text-muted/40" />
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-lg font-medium text-app-text mb-1">No lessons yet</p>
|
|
|
|
|
<p className="text-sm text-app-text-muted mb-4">
|
|
|
|
|
Start building your course by adding the first lesson.
|
|
|
|
|
</p>
|
|
|
|
|
<Button
|
|
|
|
|
className="rounded-xl bg-app-primary text-white hover:bg-app-primary-dark"
|
|
|
|
|
onClick={() => navigate(`/courses/${courseId}/lessons/new`)}
|
|
|
|
|
>
|
|
|
|
|
<HiOutlinePlus className="w-4 h-4 mr-2" />
|
|
|
|
|
Add First Lesson
|
|
|
|
|
</Button>
|
|
|
|
|
</Card>
|
2026-08-19 21:15:42 +07:00
|
|
|
) : (
|
2026-09-08 20:39:30 +07:00
|
|
|
<Card
|
|
|
|
|
className="rounded-xl border border-app-border bg-white overflow-hidden"
|
|
|
|
|
style={{ boxShadow: 'rgba(0,0,0,0.03) 0px 4px 24px' }}
|
|
|
|
|
>
|
|
|
|
|
{/* Table Header */}
|
|
|
|
|
<div className="grid grid-cols-12 gap-4 px-5 py-3 bg-app-surface-muted/50 border-b border-app-border text-xs font-semibold text-app-text-muted uppercase tracking-wider">
|
|
|
|
|
<div className="col-span-1">#</div>
|
|
|
|
|
<div className="col-span-1">Type</div>
|
|
|
|
|
<div className="col-span-4">Title</div>
|
|
|
|
|
<div className="col-span-2">Duration</div>
|
|
|
|
|
<div className="col-span-2">Status</div>
|
|
|
|
|
<div className="col-span-1">Preview</div>
|
|
|
|
|
<div className="col-span-1 text-right">Actions</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Table Rows */}
|
|
|
|
|
{lessons.map((lesson) => {
|
2026-09-09 22:16:47 +07:00
|
|
|
const tc = typeConfig[lesson.type] || typeConfig.text;
|
2026-09-08 20:39:30 +07:00
|
|
|
const TypeIcon = tc.icon;
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
key={lesson.id}
|
|
|
|
|
className="grid grid-cols-12 gap-4 px-5 py-3.5 border-b border-app-border/50 last:border-b-0 items-center hover:bg-app-surface-muted/30 transition-colors"
|
|
|
|
|
>
|
|
|
|
|
<div className="col-span-1 text-sm text-app-text-muted font-mono">
|
|
|
|
|
{lesson.position}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="col-span-1">
|
|
|
|
|
<span
|
|
|
|
|
className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-lg text-[11px] font-semibold uppercase ${tc.color}`}
|
|
|
|
|
>
|
|
|
|
|
<TypeIcon className="w-3 h-3" />
|
|
|
|
|
{tc.label}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="col-span-4">
|
|
|
|
|
<span className="text-sm font-medium text-app-text truncate block">
|
|
|
|
|
{lesson.title}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="col-span-2 text-sm text-app-text-muted font-mono">
|
|
|
|
|
{lesson.duration}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="col-span-2">
|
|
|
|
|
<Badge
|
|
|
|
|
className={`rounded-[6px] text-[11px] font-medium ${
|
|
|
|
|
statusStyles[lesson.status]
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{lesson.status}
|
|
|
|
|
</Badge>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="col-span-1">
|
|
|
|
|
{lesson.isFreePreview && (
|
|
|
|
|
<Badge className="rounded-full bg-blue-50 text-blue-600 text-[10px]">
|
|
|
|
|
Free
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="col-span-1 flex items-center justify-end gap-1">
|
|
|
|
|
<button
|
|
|
|
|
onClick={() =>
|
2026-09-22 22:37:36 +07:00
|
|
|
navigate(`/courses/${courseId}/lessons/edit/${lesson.id}`)
|
2026-09-08 20:39:30 +07:00
|
|
|
}
|
|
|
|
|
className="w-8 h-8 rounded-lg flex items-center justify-center text-app-text-muted hover:text-app-primary hover:bg-app-primary-soft transition-colors"
|
|
|
|
|
title="Edit"
|
|
|
|
|
>
|
|
|
|
|
<HiOutlinePencil className="w-4 h-4" />
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
className="w-8 h-8 rounded-lg flex items-center justify-center text-app-text-muted hover:text-app-primary hover:bg-app-primary-soft transition-colors"
|
|
|
|
|
title="Preview"
|
|
|
|
|
>
|
|
|
|
|
<HiOutlineEye className="w-4 h-4" />
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
2026-09-09 22:16:47 +07:00
|
|
|
onClick={() => handleDeleteLesson(lesson.id)}
|
2026-09-08 20:39:30 +07:00
|
|
|
className="w-8 h-8 rounded-lg flex items-center justify-center text-app-text-muted hover:text-red-500 hover:bg-red-50 transition-colors"
|
|
|
|
|
title="Delete"
|
|
|
|
|
>
|
|
|
|
|
<HiOutlineTrash className="w-4 h-4" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</Card>
|
2026-08-19 21:15:42 +07:00
|
|
|
)}
|
2026-09-03 21:49:25 +07:00
|
|
|
</div>
|
2026-09-08 20:39:30 +07:00
|
|
|
</TabsContent>
|
|
|
|
|
</Tabs>
|
2026-08-19 21:15:42 +07:00
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default CourseDetail;
|