mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
Adjusted
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
import { useEffect } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import {
|
||||
ArrowLeft, House, Pencil, Clock, BookOpen, Layers,
|
||||
BadgeCheck, Tag, Star, Lock, ListChecks,
|
||||
} from "lucide-react";
|
||||
|
||||
import { useCourses } from "@/contexts/AdminCoursesContext";
|
||||
import AppBreadcrumb from "@/components/generic/Breadcrumb/AppBreadcrumb";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
function InfoRow({ label, children }) {
|
||||
return (
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span className="text-xs text-muted-foreground uppercase tracking-wide">{label}</span>
|
||||
<span className="text-sm font-medium">{children ?? <span className="text-muted-foreground italic">—</span>}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SectionCard({ icon: Icon, title, children }) {
|
||||
return (
|
||||
<div className="rounded-lg border bg-card p-5 space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
{Icon && <Icon className="h-4 w-4 text-muted-foreground" />}
|
||||
<h2 className="text-sm font-semibold">{title}</h2>
|
||||
</div>
|
||||
<Separator />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const LEVEL_BADGE = {
|
||||
beginner: "secondary",
|
||||
intermediate: "outline",
|
||||
advanced: "destructive",
|
||||
};
|
||||
|
||||
const SUBSCRIPTION_BADGE = {
|
||||
free: "secondary",
|
||||
premium: "default",
|
||||
};
|
||||
|
||||
function LoadingSkeleton() {
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
<Skeleton className="h-8 w-64" />
|
||||
<Skeleton className="h-4 w-40" />
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{[...Array(4)].map((_, i) => <Skeleton key={i} className="h-12 w-full" />)}
|
||||
</div>
|
||||
<Skeleton className="h-32 w-full" />
|
||||
<Skeleton className="h-32 w-full" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Page ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export default function ViewCourse() {
|
||||
const navigate = useNavigate();
|
||||
const { courseId } = useParams();
|
||||
const { fetchCourse, course, loading } = useCourses();
|
||||
|
||||
useEffect(() => {
|
||||
fetchCourse(courseId);
|
||||
}, [courseId]);
|
||||
|
||||
return (
|
||||
<section className="bg-muted/60 min-h-full">
|
||||
<div className="lg:container lg:mx-auto lg:px-0 flex flex-col items-center px-4 py-10">
|
||||
|
||||
<div className="w-full max-w-2xl">
|
||||
{/* ── Header ── */}
|
||||
<div className="flex items-start justify-between gap-3 mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<Button type="button" variant="ghost" size="icon" onClick={() => navigate(-1)}>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold">Course Details</h1>
|
||||
<p className="text-sm text-muted-foreground">View course information.</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => navigate(`/admin/courses/${courseId}/edit`)}
|
||||
disabled={loading}
|
||||
>
|
||||
<Pencil className="h-4 w-4 mr-2" />
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{loading && !course ? (
|
||||
<LoadingSkeleton />
|
||||
) : !course ? (
|
||||
<div className="text-sm text-muted-foreground">Course not found.</div>
|
||||
) : (
|
||||
<div className="space-y-5">
|
||||
|
||||
{/* ── Basic Info ── */}
|
||||
<SectionCard icon={BookOpen} title="Basic Information">
|
||||
<div className="space-y-4">
|
||||
<InfoRow label="Title">{course.title}</InfoRow>
|
||||
|
||||
{course.description && (
|
||||
<InfoRow label="Description">
|
||||
<span className="whitespace-pre-wrap text-sm font-normal text-foreground">
|
||||
{course.description}
|
||||
</span>
|
||||
</InfoRow>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<InfoRow label="Course Code">
|
||||
{course.course_code ?? <span className="text-muted-foreground italic text-sm">—</span>}
|
||||
</InfoRow>
|
||||
<InfoRow label="Order Index">{course.order_index ?? 0}</InfoRow>
|
||||
</div>
|
||||
</div>
|
||||
</SectionCard>
|
||||
|
||||
{/* ── Settings ── */}
|
||||
<SectionCard icon={Tag} title="Settings">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<InfoRow label="Level">
|
||||
{course.level ? (
|
||||
<Badge variant={LEVEL_BADGE[course.level] ?? "outline"} className="capitalize mt-0.5">
|
||||
{course.level}
|
||||
</Badge>
|
||||
) : null}
|
||||
</InfoRow>
|
||||
|
||||
<InfoRow label="Subscription">
|
||||
<Badge variant={SUBSCRIPTION_BADGE[course.subscription] ?? "outline"} className="capitalize mt-0.5">
|
||||
{course.subscription ?? "free"}
|
||||
</Badge>
|
||||
</InfoRow>
|
||||
</div>
|
||||
</SectionCard>
|
||||
|
||||
{/* ── Duration & Stats ── */}
|
||||
<SectionCard icon={Clock} title="Duration & Stats">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<InfoRow label="Duration">
|
||||
{course.duration_formatted ?? course.duration_seconds
|
||||
? `${course.duration_seconds}s`
|
||||
: "—"}
|
||||
</InfoRow>
|
||||
<InfoRow label="Units">
|
||||
<div className="flex items-center gap-1.5 mt-0.5">
|
||||
<Layers className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
{course.unitCount ?? course.units?.length ?? 0}
|
||||
</div>
|
||||
</InfoRow>
|
||||
<InfoRow label="Lessons">
|
||||
<div className="flex items-center gap-1.5 mt-0.5">
|
||||
<BookOpen className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
{course.lessonCount ?? 0}
|
||||
</div>
|
||||
</InfoRow>
|
||||
</div>
|
||||
</SectionCard>
|
||||
|
||||
{/* ── Objectives ── */}
|
||||
{course.objectives?.length > 0 && (
|
||||
<SectionCard icon={ListChecks} title="Learning Objectives">
|
||||
<ul className="space-y-2">
|
||||
{course.objectives.map((obj, i) => (
|
||||
<li key={obj.objective_id ?? i} className="flex items-start gap-2 text-sm">
|
||||
<BadgeCheck className="h-4 w-4 text-primary mt-0.5 shrink-0" />
|
||||
{obj.text}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</SectionCard>
|
||||
)}
|
||||
|
||||
{/* ── Prerequisites ── */}
|
||||
{course.prerequisites?.length > 0 && (
|
||||
<SectionCard icon={Star} title="Prerequisites">
|
||||
<ul className="space-y-2">
|
||||
{course.prerequisites.map((p, i) => (
|
||||
<li key={p.prereq_id ?? i} className="flex items-center gap-2 text-sm">
|
||||
<Badge variant="outline" className="capitalize text-xs">{p.ref_type}</Badge>
|
||||
<span className="text-muted-foreground">ID: {p.ref_id}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</SectionCard>
|
||||
)}
|
||||
|
||||
{/* ── Assessment ── */}
|
||||
{course.assessment && (
|
||||
<SectionCard icon={Lock} title="Final Assessment">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<InfoRow label="Title">{course.assessment.title ?? "Untitled Assessment"}</InfoRow>
|
||||
<InfoRow label="Required">
|
||||
<Badge variant={course.assessment.is_required ? "default" : "secondary"}>
|
||||
{course.assessment.is_required ? "Required" : "Optional"}
|
||||
</Badge>
|
||||
</InfoRow>
|
||||
<InfoRow label="Passing Score">{course.assessment.passing_score ?? 70}%</InfoRow>
|
||||
<InfoRow label="Time Limit">
|
||||
{course.assessment.time_limit_minutes
|
||||
? `${course.assessment.time_limit_minutes} mins`
|
||||
: "No limit"}
|
||||
</InfoRow>
|
||||
</div>
|
||||
</SectionCard>
|
||||
)}
|
||||
|
||||
{/* ── Audit ── */}
|
||||
<SectionCard icon={BadgeCheck} title="Audit">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<InfoRow label="Created By">{course.creator?.full_name ?? course.createdBy ?? "—"}</InfoRow>
|
||||
<InfoRow label="Updated By">{course.updater?.full_name ?? course.updatedBy ?? "—"}</InfoRow>
|
||||
<InfoRow label="Created At">
|
||||
{course.createdAt ? new Date(course.createdAt).toLocaleString() : "—"}
|
||||
</InfoRow>
|
||||
<InfoRow label="Updated At">
|
||||
{course.updatedAt ? new Date(course.updatedAt).toLocaleString() : "—"}
|
||||
</InfoRow>
|
||||
</div>
|
||||
</SectionCard>
|
||||
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user