"use client"; // app/library/page.jsx — everything you've made, grouped by subject, saved locally. import { useEffect, useMemo, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { IconSearch, IconPlus, IconCopy, IconTrash, IconArrowRight } from "@tabler/icons-react"; import { groupBySubject } from "@/lib/group"; const TYPE_LABELS = { quiz: "Quiz", test: "Test", worksheet: "Worksheet", discussion: "Discussion", case_study: "Case study", }; const TYPE_CHIP = { quiz: "chip", test: "chip chip-red", worksheet: "chip chip-gold", discussion: "chip chip-neutral", case_study: "chip chip-neutral", }; const FILTERS = [ { id: "all", label: "All" }, { id: "quiz", label: "Quizzes" }, { id: "test", label: "Tests" }, { id: "worksheet", label: "Worksheets" }, ]; function SkeletonGroup() { return (
{[0, 1].map((i) => (
))}
); } export default function LibraryPage() { const router = useRouter(); const [items, setItems] = useState(null); const [query, setQuery] = useState(""); const [filter, setFilter] = useState("all"); const [error, setError] = useState(""); const [busy, setBusy] = useState(""); function load() { fetch("/api/assignments") .then((r) => r.json()) .then((d) => setItems(d.assignments || [])) .catch(() => setError("Could not load your library.")); } useEffect(load, []); async function duplicate(id) { setBusy(id); setError(""); try { const res = await fetch("/api/assignments/" + id); const full = await res.json(); if (!res.ok) throw new Error(full.error || "Could not load that assignment."); const { id: _id, createdAt, updatedAt, ...copy } = full; copy.title = (copy.title || "Untitled") + " (copy)"; const res2 = await fetch("/api/assignments", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(copy) }); const created = await res2.json(); if (!res2.ok) throw new Error(created.error || "Could not duplicate."); load(); } catch (e) { setError(String(e.message || e)); } finally { setBusy(""); } } async function remove(id, title) { if (!confirm(`Delete "${title}"? This can't be undone.`)) return; setBusy(id); try { await fetch("/api/assignments/" + id, { method: "DELETE" }); load(); } finally { setBusy(""); } } const filtered = useMemo(() => { const q = query.trim().toLowerCase(); return (items || []).filter((a) => { if (filter !== "all" && a.assignmentType !== filter) return false; if (!q) return true; return [a.title, a.subject, a.gradeLevel, TYPE_LABELS[a.assignmentType]] .filter(Boolean).join(" ").toLowerCase().includes(q); }); }, [items, query, filter]); const groups = useMemo(() => groupBySubject(filtered), [filtered]); return (

Library

Everything you’ve created, stored locally on this computer.

New assignment
{error &&
{error}
} {items === null && (<>)} {items !== null && items.length === 0 && (

Nothing here yet

Create your first assignment and it will be saved here automatically.

Create an assignment
)} {items !== null && items.length > 0 && ( <>
setQuery(e.target.value)} aria-label="Search library" />
{FILTERS.map((f) => ( ))}
{groups.length === 0 &&

No matches{query ? <> for “{query}” : ""}.

} {groups.map((g) => (

{g.key}

{g.items.length}
{g.items.map((a) => (
{TYPE_LABELS[a.assignmentType] || a.assignmentType}
{a.title}

{[a.gradeLevel, a.subject].filter(Boolean).join(" · ")} {(a.gradeLevel || a.subject) ? " · " : ""} {a.questionCount} question{a.questionCount === 1 ? "" : "s"} · {a.totalPoints} pts · updated {formatDate(a.updatedAt)}

))}
))} )}
); } function formatDate(iso) { try { return new Date(iso).toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" }); } catch { return ""; } }