"use client"; import dynamic from "next/dynamic"; import Link from "next/link"; import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { AlertTriangle, Bookmark, ChevronDown, ClipboardList, ExternalLink, FolderOpen, Loader2, MessageSquare, Pencil, Plus, Trash2, X, } from "lucide-react"; import SpaceSectionHeader from "@/components/space/SpaceSectionHeader"; import { createCategory, deleteCategory, deleteNotebookEntry, listCategories, listNotebookEntries, removeEntryFromCategory, renameCategory, updateNotebookEntry, type NotebookCategory, type NotebookEntry, } from "@/lib/notebook-api"; const MarkdownRenderer = dynamic( () => import("@/components/common/MarkdownRenderer"), { ssr: false }, ); type FilterMode = "all" | "bookmarked" | "wrong"; const FILTERS: { mode: FilterMode; label: string }[] = [ { mode: "all", label: "All" }, { mode: "bookmarked", label: "Bookmarked" }, { mode: "wrong", label: "Wrong Only" }, ]; export default function QuestionBankSection() { const { t } = useTranslation(); const [items, setItems] = useState([]); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(true); const [errorMsg, setErrorMsg] = useState(null); const [filter, setFilter] = useState("all"); const [activeCategoryId, setActiveCategoryId] = useState(null); const [categories, setCategories] = useState([]); const [pendingId, setPendingId] = useState(null); const [showCategoryManager, setShowCategoryManager] = useState(false); const [newCatName, setNewCatName] = useState(""); const [renamingCat, setRenamingCat] = useState<{ id: number; name: string; } | null>(null); const loadCategoriesData = useCallback(async () => { try { setCategories(await listCategories()); } catch { /* ignore */ } }, []); const loadItems = useCallback( async (mode: FilterMode, catId: number | null) => { setErrorMsg(null); try { const response = await listNotebookEntries({ bookmarked: mode === "bookmarked" ? true : undefined, is_correct: mode === "wrong" ? false : undefined, category_id: catId ?? undefined, limit: 200, }); setItems(response.items); setTotal(response.total); } catch (err) { setErrorMsg(String(err instanceof Error ? err.message : err)); } finally { setLoading(false); } }, [], ); useEffect(() => { void loadItems(filter, activeCategoryId); void loadCategoriesData(); }, [filter, activeCategoryId, loadItems, loadCategoriesData]); const handleToggleBookmark = useCallback( async (item: NotebookEntry) => { const next = !item.bookmarked; setPendingId(item.id); try { await updateNotebookEntry(item.id, { bookmarked: next }); setItems((prev) => filter === "bookmarked" && !next ? prev.filter((e) => e.id !== item.id) : prev.map((e) => e.id === item.id ? { ...e, bookmarked: next } : e, ), ); if (filter === "bookmarked" && !next) setTotal((p) => Math.max(0, p - 1)); } catch { /* ignore */ } setPendingId(null); }, [filter], ); const handleDelete = useCallback( async (item: NotebookEntry) => { if (!window.confirm(t("Delete this entry?"))) return; setPendingId(item.id); try { await deleteNotebookEntry(item.id); setItems((prev) => prev.filter((e) => e.id !== item.id)); setTotal((p) => Math.max(0, p - 1)); } catch { /* ignore */ } setPendingId(null); }, [t], ); const handleRemoveFromCategory = useCallback( async (item: NotebookEntry) => { if (activeCategoryId === null) return; setPendingId(item.id); try { await removeEntryFromCategory(item.id, activeCategoryId); setItems((prev) => prev.filter((e) => e.id !== item.id)); setTotal((p) => Math.max(0, p - 1)); } catch { /* ignore */ } setPendingId(null); }, [activeCategoryId], ); const handleCreateCategory = useCallback(async () => { if (!newCatName.trim()) return; try { await createCategory(newCatName.trim()); setNewCatName(""); await loadCategoriesData(); } catch { /* ignore */ } }, [loadCategoriesData, newCatName]); const handleRenameCategory = useCallback(async () => { if (!renamingCat || !renamingCat.name.trim()) return; try { await renameCategory(renamingCat.id, renamingCat.name.trim()); setRenamingCat(null); await loadCategoriesData(); } catch { /* ignore */ } }, [loadCategoriesData, renamingCat]); const handleDeleteCategory = useCallback( async (catId: number) => { if (!window.confirm(t("Delete this category?"))) return; try { await deleteCategory(catId); if (activeCategoryId === catId) setActiveCategoryId(null); await loadCategoriesData(); } catch { /* ignore */ } }, [activeCategoryId, loadCategoriesData, t], ); return (
{total} {t("questions.count.suffix")} } /> {/* Category manager */}
{showCategoryManager && (
{categories.map((cat) => (
{renamingCat?.id === cat.id ? ( setRenamingCat({ ...renamingCat, name: e.target.value, }) } onKeyDown={(e) => { if (e.key === "Enter") void handleRenameCategory(); if (e.key === "Escape") setRenamingCat(null); }} onBlur={() => void handleRenameCategory()} className="flex-1 rounded border border-[var(--border)] bg-[var(--background)] px-2 py-0.5 text-[12px] text-[var(--foreground)] outline-none" /> ) : ( {cat.name} ({cat.entry_count}) )}
))} {!categories.length && (

{t("No categories yet.")}

)}
setNewCatName(e.target.value)} onKeyDown={(e) => e.key === "Enter" && void handleCreateCategory() } placeholder={t("New category name...")} className="flex-1 rounded-lg border border-[var(--border)] bg-[var(--background)] px-3 py-1.5 text-[12px] text-[var(--foreground)] outline-none placeholder:text-[var(--muted-foreground)]" />
)}
{/* Filter bar */}
{FILTERS.map(({ mode, label }) => { const active = filter === mode && activeCategoryId === null; return ( ); })} {categories.length > 0 && ( | )} {categories.map((cat) => { const active = activeCategoryId === cat.id; return ( ); })}
{t("Total")}: {total}
{/* Content */} {loading ? (
) : errorMsg ? (

{t("Failed to load entries")}

{errorMsg}

) : items.length === 0 ? (

{t("No entries yet")}

{t("Questions from your quizzes will appear here.")}

) : (
    {items.map((item) => { const disabled = pendingId === item.id; return (
  • {item.difficulty && ( {item.difficulty} )} {item.question_type && ( {item.question_type} )} {item.is_correct ? t("Correct") : t("Incorrect")}
    {activeCategoryId !== null && ( )}
    {item.options && Object.keys(item.options).length > 0 && (
    {Object.entries(item.options).map(([key, text]) => { const isUserAnswer = item.user_answer?.toUpperCase() === key.toUpperCase(); const isCorrectAnswer = item.correct_answer?.toUpperCase() === key.toUpperCase(); const isWrongPick = isUserAnswer && !item.is_correct; return (
    {key}. {text} {isCorrectAnswer && ( ✓ {t("Correct")} )} {isWrongPick && ( ✗ {t("Your pick")} )}
    ); })}
    )} {(!item.options || Object.keys(item.options).length === 0) && (
    {t("Your Answer")} {item.is_correct ? "✓" : "✗"}
    {item.user_answer ? ( item.question_type === "coding" ? ( ) : ( ) ) : ( )}
    {t("Reference Answer")}
    {item.correct_answer ? ( item.question_type === "coding" ? ( ) : ( ) ) : ( )}
    )} {item.explanation && (
    {t("Explanation")}
    )}
    {item.session_title || t("Original Session")} {item.followup_session_id && ( {t("Follow-up")} )}
    {new Date(item.created_at * 1000).toLocaleString()}
  • ); })}
)}
); }