"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 (
);
}
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 && (
<>
{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 ""; }
}