import { useCallback, useEffect, useMemo, useState } from "react" import { ChevronDown, ChevronLeft, ChevronRight, RefreshCw } from "lucide-react" import spinnerSrc from "../../assets/Circular-indeterminate progress indicator.svg" 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 { Dialog, DialogContent, DialogHeader, DialogTitle } from "../../components/ui/dialog" import { getQuestionSetById, getQuestionSets } from "../../api/courses.api" import type { QuestionSet, QuestionSetDetail } from "../../types/course.types" import { cn } from "../../lib/utils" import { SpinnerIcon } from "../../components/ui/spinner-icon" const statusColor: Record = { PUBLISHED: "bg-green-100 text-green-700", DRAFT: "bg-amber-100 text-amber-700", ARCHIVED: "bg-grayScale-200 text-grayScale-600", } export function PracticeDetailsPage() { const [practices, setPractices] = useState([]) const [selectedPracticeId, setSelectedPracticeId] = useState(null) const [selectedPracticeDetail, setSelectedPracticeDetail] = useState(null) const [detailOpen, setDetailOpen] = useState(false) const [loadingList, setLoadingList] = useState(false) const [loadingDetail, setLoadingDetail] = useState(false) const [searchQuery, setSearchQuery] = useState("") const [statusFilter, setStatusFilter] = useState("all") const [ownerTypeFilter, setOwnerTypeFilter] = useState("all") const [page, setPage] = useState(1) const [pageSize, setPageSize] = useState(10) const fetchPractices = useCallback(async () => { setLoadingList(true) try { const batchSize = 100 let offset = 0 let total = Number.POSITIVE_INFINITY const sets: QuestionSet[] = [] while (sets.length < total) { const res = await getQuestionSets({ set_type: "PRACTICE", limit: batchSize, offset }) const payload = res.data?.data as unknown let chunk: QuestionSet[] = [] let chunkTotal = 0 if (Array.isArray(payload)) { chunk = payload as QuestionSet[] chunkTotal = chunk.length } else if ( payload && typeof payload === "object" && Array.isArray((payload as { question_sets?: unknown[] }).question_sets) ) { const mapped = payload as { question_sets: QuestionSet[]; total_count?: number } chunk = mapped.question_sets chunkTotal = mapped.total_count ?? chunk.length } sets.push(...chunk) total = chunkTotal if (chunk.length < batchSize) break offset += chunk.length } setPractices(sets) if (sets.length > 0) { setSelectedPracticeId((prev) => prev ?? sets[0].id) } else { setSelectedPracticeId(null) setSelectedPracticeDetail(null) } } catch (error) { console.error("Failed to fetch practices:", error) setPractices([]) setSelectedPracticeId(null) setSelectedPracticeDetail(null) } finally { setLoadingList(false) } }, []) const fetchPracticeDetail = useCallback(async (practiceId: number) => { setLoadingDetail(true) try { const res = await getQuestionSetById(practiceId) setSelectedPracticeDetail(res.data?.data ?? null) } catch (error) { console.error("Failed to fetch practice detail:", error) setSelectedPracticeDetail(null) } finally { setLoadingDetail(false) } }, []) useEffect(() => { fetchPractices() }, [fetchPractices]) useEffect(() => { if (selectedPracticeId) { fetchPracticeDetail(selectedPracticeId) } }, [selectedPracticeId, fetchPracticeDetail]) useEffect(() => { setPage(1) }, [searchQuery, statusFilter, ownerTypeFilter]) const filteredPractices = useMemo(() => { return practices.filter((practice) => { const matchesSearch = !searchQuery.trim() || practice.title.toLowerCase().includes(searchQuery.toLowerCase()) || (practice.description || "").toLowerCase().includes(searchQuery.toLowerCase()) || String(practice.id).includes(searchQuery) || String(practice.owner_id).includes(searchQuery) const matchesStatus = statusFilter === "all" || practice.status === statusFilter const matchesOwnerType = ownerTypeFilter === "all" || practice.owner_type === ownerTypeFilter return matchesSearch && matchesStatus && matchesOwnerType }) }, [practices, searchQuery, statusFilter, ownerTypeFilter]) const totalCount = useMemo(() => filteredPractices.length, [filteredPractices]) const totalPages = Math.max(1, Math.ceil(totalCount / pageSize)) const safePage = Math.min(page, totalPages) const paginatedPractices = useMemo(() => { const start = (safePage - 1) * pageSize return filteredPractices.slice(start, start + pageSize) }, [filteredPractices, 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 } return (

Practice Management

Browse all practice question sets and view their details.

Practices ({totalCount})
setSearchQuery(e.target.value)} placeholder="Search by title, description, practice ID, or owner ID..." />
Title Owner Status Created {loadingList ? (
Loading practices...
) : filteredPractices.length === 0 ? ( No practice sets found. ) : ( paginatedPractices.map((practice) => ( { setSelectedPracticeId(practice.id) setDetailOpen(true) }} className={cn( "group cursor-pointer", selectedPracticeId === practice.id && "bg-brand-100/30", )} >

{practice.title}

{practice.description || "—"}

{practice.owner_type} #{practice.owner_id} {practice.status} {practice.created_at}
)) )}
Showing {startEntry}-{endEntry} of {totalCount} entries Rows per page
{getPageNumbers().map((n, idx) => typeof n === "string" ? ( ... ) : ( ), )}
Practice Detail {!selectedPracticeId ? (

Select a practice from the list to view details.

) : loadingDetail ? (

Loading detail...

) : !selectedPracticeDetail ? (

Failed to load practice detail.

) : (

Title

{selectedPracticeDetail.title}

Set Type

{selectedPracticeDetail.set_type}

Description

{selectedPracticeDetail.description || "—"}

Owner

{selectedPracticeDetail.owner_type} #{selectedPracticeDetail.owner_id}

Status

{selectedPracticeDetail.status}

Question Count

{selectedPracticeDetail.question_count ?? 0}

Created At

{selectedPracticeDetail.created_at}

)}
) }