"use client"; import { useEffect, useMemo, useState } from "react"; import { Bookmark, Check, ClipboardList, FolderOpen, Loader2, Search, } from "lucide-react"; import { useTranslation } from "react-i18next"; import PickerShell from "@/components/common/PickerShell"; import PickerHeader from "@/components/common/PickerHeader"; import { listCategories, listNotebookEntries, type NotebookCategory, type NotebookEntry, } from "@/lib/notebook-api"; export interface SelectedQuestionEntry { id: number; question: string; session_title: string; is_correct: boolean; difficulty: string; } interface QuestionBankPickerProps { open: boolean; onClose: () => void; onApply: (entries: SelectedQuestionEntry[]) => void; } type FilterMode = "all" | "bookmarked" | "wrong"; const FILTER_MODES: { value: FilterMode; label: string }[] = [ { value: "all", label: "All" }, { value: "bookmarked", label: "Bookmarked" }, { value: "wrong", label: "Wrong Only" }, ]; export default function QuestionBankPicker({ open, onClose, onApply, }: QuestionBankPickerProps) { const { t } = useTranslation(); const [entries, setEntries] = useState([]); const [categories, setCategories] = useState([]); const [filter, setFilter] = useState("all"); const [activeCategoryId, setActiveCategoryId] = useState(null); const [selectedIds, setSelectedIds] = useState([]); const [query, setQuery] = useState(""); const [loading, setLoading] = useState(false); useEffect(() => { if (!open) return; let mounted = true; void (async () => { try { setCategories(await listCategories()); } catch { if (mounted) setCategories([]); } })(); return () => { mounted = false; }; }, [open]); useEffect(() => { if (!open) return; let mounted = true; setLoading(true); void (async () => { try { const result = await listNotebookEntries({ bookmarked: filter === "bookmarked" ? true : undefined, is_correct: filter === "wrong" ? false : undefined, category_id: activeCategoryId ?? undefined, limit: 200, }); if (!mounted) return; setEntries(result.items); } catch { if (!mounted) return; setEntries([]); } finally { if (mounted) setLoading(false); } })(); return () => { mounted = false; }; }, [open, filter, activeCategoryId]); const filteredEntries = useMemo(() => { const keyword = query.trim().toLowerCase(); if (!keyword) return entries; return entries.filter((entry) => { const question = String(entry.question || "").toLowerCase(); const session = String(entry.session_title || "").toLowerCase(); return question.includes(keyword) || session.includes(keyword); }); }, [entries, query]); const toggleEntry = (entryId: number) => { setSelectedIds((prev) => prev.includes(entryId) ? prev.filter((id) => id !== entryId) : [...prev, entryId], ); }; const handleApply = () => { const selectedSet = new Set(selectedIds); const selectedEntries = entries .filter((entry) => selectedSet.has(entry.id)) .map((entry) => ({ id: entry.id, question: entry.question, session_title: entry.session_title, is_correct: entry.is_correct, difficulty: entry.difficulty || "", })); onApply(selectedEntries); onClose(); }; return (
{/* Filter row */}
{FILTER_MODES.map(({ value, label }) => { const active = filter === value && activeCategoryId === null; return ( ); })} {categories.length > 0 && ( | )} {categories.map((cat) => { const active = activeCategoryId === cat.id; return ( ); })}
setQuery(event.target.value)} placeholder={t("Search questions by content")} className="w-full rounded-xl border border-[var(--border)] bg-[var(--card)] py-2.5 pl-9 pr-3 text-[13px] text-[var(--foreground)] outline-none transition focus:border-[var(--primary)]/50 focus:ring-2 focus:ring-[var(--primary)]/15" />
{loading ? (
) : filteredEntries.length ? (
{filteredEntries.map((entry) => { const selected = selectedIds.includes(entry.id); return ( ); })}
) : (
{entries.length === 0 ? t("No quiz entries yet.") : t("No matching questions found.")}
)}
{selectedIds.length === 1 ? t("1 question selected") : t("{{n}} questions selected", { n: selectedIds.length })}
); }