@@ -307,7 +660,12 @@ export const Practice = () => {
Take a full adaptive test and benchmark your SAT readiness.
-
+
{/* ── Practice modes ── */}
@@ -322,19 +680,22 @@ export const Practice = () => {
className={`pr-mode-card ${card.color}`}
onClick={() => navigate(card.route)}
>
-
-
- {card.icon}
-
-
- {card.badge}
-
+ {/* Illustration */}
+
+
-
-
{card.title}
+
+ {/* Body */}
+
+
+
+ {card.icon}
+
+
{card.title}
+
{card.desc}
+
{card.arrow}
-
{card.arrow}
))}
diff --git a/src/pages/student/practice-sheet/page.tsx b/src/pages/student/practice-sheet/page.tsx
new file mode 100644
index 0000000..1e0c6fb
--- /dev/null
+++ b/src/pages/student/practice-sheet/page.tsx
@@ -0,0 +1,1009 @@
+import { useEffect, useMemo, useState } from "react";
+import {
+ BookOpen,
+ Clock,
+ FileText,
+ Layers,
+ LayoutGrid,
+ List,
+ Lock,
+ PlayCircle,
+ Search,
+ Sparkles,
+ Tag,
+ X,
+ Zap,
+} from "lucide-react";
+import { useNavigate } from "react-router-dom";
+import { api } from "../../../utils/api";
+import { type PracticeSheet } from "../../../types/sheet";
+
+/* ─────────────────────────────────────────────
+ Ambient decoration
+───────────────────────────────────────────── */
+const DOTS = [
+ { size: 10, color: "#f97316", top: "7%", left: "4%", delay: "0s" },
+ { size: 7, color: "#a855f7", top: "28%", left: "2%", delay: "1.2s" },
+ { size: 9, color: "#22c55e", top: "60%", left: "3%", delay: "0.6s" },
+ { size: 12, color: "#3b82f6", top: "11%", right: "4%", delay: "1.8s" },
+ { size: 7, color: "#f43f5e", top: "47%", right: "2%", delay: "0.9s" },
+ { size: 9, color: "#eab308", top: "76%", right: "5%", delay: "0.4s" },
+];
+
+/* ─────────────────────────────────────────────
+ Helpers
+───────────────────────────────────────────── */
+const DIFF_META: Record<
+ string,
+ { label: string; color: string; bg: string; border: string }
+> = {
+ EASY: { label: "Easy", color: "#16a34a", bg: "#f0fdf4", border: "#bbf7d0" },
+ MEDIUM: {
+ label: "Medium",
+ color: "#d97706",
+ bg: "#fffbeb",
+ border: "#fde68a",
+ },
+ HARD: { label: "Hard", color: "#dc2626", bg: "#fff5f5", border: "#fecaca" },
+};
+const getDiff = (d: string) =>
+ DIFF_META[d?.toUpperCase()] ?? {
+ label: d,
+ color: "#6b7280",
+ bg: "#f9fafb",
+ border: "#e5e7eb",
+ };
+
+const STATUS_META: Record<
+ string,
+ { label: string; color: string; bg: string }
+> = {
+ COMPLETED: { label: "Completed", color: "#16a34a", bg: "#f0fdf4" },
+ IN_PROGRESS: { label: "In Progress", color: "#d97706", bg: "#fffbeb" },
+ NOT_STARTED: { label: "Not Started", color: "#9ca3af", bg: "#f9fafb" },
+};
+const getStatus = (s: string) =>
+ STATUS_META[s?.toUpperCase()] ?? {
+ label: s ?? "—",
+ color: "#9ca3af",
+ bg: "#f9fafb",
+ };
+
+const PALETTES = [
+ {
+ bg: "#fff5f5",
+ blob: "#fee2e2",
+ accent: "#fca5a5",
+ pop: "#ef4444",
+ line: "#fecaca",
+ },
+ {
+ bg: "#ecfeff",
+ blob: "#cffafe",
+ accent: "#67e8f9",
+ pop: "#06b6d4",
+ line: "#a5f3fc",
+ },
+ {
+ bg: "#f7ffe4",
+ blob: "#d9f99d",
+ accent: "#bef264",
+ pop: "#84cc16",
+ line: "#d9f99d",
+ },
+ {
+ bg: "#fffbeb",
+ blob: "#fef3c7",
+ accent: "#fde68a",
+ pop: "#f59e0b",
+ line: "#fcd34d",
+ },
+];
+
+const formatTime = (mins: number) => {
+ if (!mins) return "—";
+ if (mins < 60) return `${mins}m`;
+ return `${Math.floor(mins / 60)}h ${mins % 60 > 0 ? `${mins % 60}m` : ""}`.trim();
+};
+
+const initials = (name: string) =>
+ name
+ ?.split(" ")
+ .map((w) => w[0])
+ .slice(0, 2)
+ .join("")
+ .toUpperCase() ?? "?";
+
+/* ─────────────────────────────────────────────
+ Card illustration
+───────────────────────────────────────────── */
+const CardIllo = ({ index, locked }: { index: number; locked: boolean }) => {
+ const p = PALETTES[index % PALETTES.length];
+ return (
+
+ );
+};
+
+/* ─────────────────────────────────────────────
+ Compact row card
+───────────────────────────────────────────── */
+const CompactCard = ({
+ sheet,
+ index,
+ onStart,
+}: {
+ sheet: PracticeSheet;
+ index: number;
+ onStart: () => void;
+}) => {
+ const p = PALETTES[index % PALETTES.length];
+ const status = getStatus(sheet.user_status);
+
+ return (
+
+
+
+
+
+ {sheet.title}
+ {sheet.is_locked && (
+
+ )}
+
+
+
+
+ {status.label}
+
+ {sheet.time_limit > 0 && (
+
+
+ {formatTime(sheet.time_limit)}
+
+ )}
+ {sheet.modules_count > 0 && (
+
+
+ {sheet.modules_count} module{sheet.modules_count !== 1 ? "s" : ""}
+
+ )}
+
+
+
+ {sheet.is_locked ? (
+
+
+
+ ) : (
+
+ )}
+
+ );
+};
+
+/* ─────────────────────────────────────────────
+ Skeletons
+───────────────────────────────────────────── */
+const SkeletonCard = () => (
+
+);
+
+const SkeletonCompact = () => (
+
+);
+
+/* ─────────────────────────────────────────────
+ Empty state
+───────────────────────────────────────────── */
+const EmptyState = ({ query }: { query: string }) => (
+
+
+
+ {query ? `No sheets match "${query}"` : "No practice sheets yet"}
+
+
Try a different search or check back later.
+
+);
+
+/* ─────────────────────────────────────────────
+ Styles
+───────────────────────────────────────────── */
+const STYLES = `
+ @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800;900&family=Nunito+Sans:wght@400;600;700&display=swap');
+
+ :root { --content-max: 1100px; }
+
+ .ps-screen {
+ min-height: 100vh;
+ background: #fffbf4;
+ font-family: 'Nunito', sans-serif;
+ position: relative;
+ overflow-x: hidden;
+ }
+ @media (min-width: 768px) {
+ .ps-screen { padding-left: calc(17rem + 1.25rem); }
+ }
+
+ .ps-blob { position:fixed;pointer-events:none;z-index:0;filter:blur(48px);opacity:0.35; }
+ .ps-blob-1 { width:240px;height:240px;background:#fde68a;top:-80px;left:-80px;border-radius:60% 40% 70% 30%/50% 60% 40% 50%;animation:psWobble1 14s ease-in-out infinite; }
+ .ps-blob-2 { width:190px;height:190px;background:#a5f3c0;bottom:-50px;left:6%;border-radius:40% 60% 30% 70%/60% 40% 60% 40%;animation:psWobble2 16s ease-in-out infinite; }
+ .ps-blob-3 { width:210px;height:210px;background:#fbcfe8;top:15%;right:-60px;border-radius:70% 30% 50% 50%/40% 60% 40% 60%;animation:psWobble1 18s ease-in-out infinite reverse; }
+ .ps-blob-4 { width:150px;height:150px;background:#bfdbfe;bottom:12%;right:2%;border-radius:50% 50% 30% 70%/60% 40% 60% 40%;animation:psWobble2 12s ease-in-out infinite; }
+
+ @keyframes psWobble1 {
+ 0%,100%{border-radius:60% 40% 70% 30%/50% 60% 40% 50%;transform:translate(0,0) rotate(0deg);}
+ 50%{border-radius:40% 60% 30% 70%/60% 40% 60% 40%;transform:translate(12px,16px) rotate(8deg);}
+ }
+ @keyframes psWobble2 {
+ 0%,100%{border-radius:40% 60% 30% 70%/60% 40% 60% 40%;transform:translate(0,0) rotate(0deg);}
+ 50%{border-radius:60% 40% 70% 30%/40% 60% 40% 60%;transform:translate(-10px,12px) rotate(-6deg);}
+ }
+
+ .ps-dot { position:fixed;border-radius:50%;pointer-events:none;z-index:0;opacity:0.3;animation:psFloat 7s ease-in-out infinite; }
+ @keyframes psFloat {
+ 0%,100%{transform:translateY(0) rotate(0deg);}
+ 50%{transform:translateY(-12px) rotate(180deg);}
+ }
+
+ .ps-inner {
+ position:relative;z-index:1;
+ max-width:580px;margin:0 auto;
+ padding:2rem 1.25rem 4rem;
+ display:flex;flex-direction:column;gap:1.5rem;
+ }
+ @media(min-width:900px){
+ .ps-inner { max-width:var(--content-max);padding:3rem 1.5rem 6rem; }
+ .ps-blob-1 { left:calc((100vw - var(--content-max)) / 2 - 120px);top:-120px;width:300px;height:300px; }
+ .ps-blob-2 { left:calc((100vw - var(--content-max)) / 2 + 20px);bottom:-80px;width:220px;height:220px; }
+ .ps-blob-3 { right:calc((100vw - var(--content-max)) / 2 - 40px);top:10%;width:260px;height:260px; }
+ .ps-blob-4 { right:calc((100vw - var(--content-max)) / 2 + 10px);bottom:6%;width:180px;height:180px; }
+ }
+
+ @keyframes psPopIn {
+ from{opacity:0;transform:scale(0.92) translateY(12px);}
+ to{opacity:1;transform:scale(1) translateY(0);}
+ }
+ .ps-anim { animation:psPopIn 0.4s cubic-bezier(0.34,1.56,0.64,1) both; }
+ .ps-anim-1 { animation-delay:0.05s; }
+ .ps-anim-2 { animation-delay:0.10s; }
+ .ps-anim-3 { animation-delay:0.15s; }
+
+ /* Page header */
+ .ps-page-header { display:flex;flex-direction:column;gap:0.3rem; }
+ .ps-eyebrow {
+ font-size:0.65rem;font-weight:800;letter-spacing:0.18em;
+ text-transform:uppercase;color:#f59e0b;
+ display:flex;align-items:center;gap:0.4rem;
+ }
+ .ps-title {
+ font-size:clamp(1.6rem,5vw,2.1rem);font-weight:900;
+ color:#1e1b4b;letter-spacing:-0.03em;line-height:1.1;
+ }
+ .ps-subtitle {
+ font-family:'Nunito Sans',sans-serif;
+ font-size:0.88rem;font-weight:600;color:#9ca3af;margin-top:0.2rem;
+ }
+
+ /* Search */
+ .ps-search-wrap { position:relative; }
+ .ps-search-icon {
+ position:absolute;left:1rem;top:50%;transform:translateY(-50%);
+ color:#9ca3af;pointer-events:none;
+ }
+ .ps-search-input {
+ width:100%;box-sizing:border-box;
+ background:white;border:2.5px solid #e9d5ff;border-radius:100px;
+ padding:0.75rem 3rem 0.75rem 2.8rem;
+ font-family:'Nunito',sans-serif;font-size:0.92rem;font-weight:700;
+ color:#1e1b4b;outline:none;
+ box-shadow:0 4px 14px rgba(0,0,0,0.04);
+ transition:border-color 0.15s,box-shadow 0.15s;
+ }
+ .ps-search-input::placeholder { color:#c4b5fd;font-weight:600; }
+ .ps-search-input:focus { border-color:#a855f7;box-shadow:0 4px 20px rgba(168,85,247,0.15); }
+ .ps-search-clear {
+ position:absolute;right:0.85rem;top:50%;transform:translateY(-50%);
+ background:#f3f4f6;border:none;border-radius:50%;width:28px;height:28px;
+ display:flex;align-items:center;justify-content:center;
+ cursor:pointer;color:#6b7280;transition:background 0.15s;
+ }
+ .ps-search-clear:hover { background:#e5e7eb; }
+
+ /* ── Toolbar ── */
+ .ps-toolbar {
+ display:flex;align-items:center;justify-content:space-between;gap:0.75rem;
+ }
+ .ps-results-meta {
+ font-size:0.78rem;font-weight:800;color:#9ca3af;letter-spacing:0.04em;
+ }
+ .ps-results-meta span { color:#7c3aed; }
+
+ .ps-view-toggle {
+ display:flex;align-items:center;
+ background:white;border:2.5px solid #e9d5ff;border-radius:100px;
+ padding:3px;gap:2px;
+ box-shadow:0 2px 8px rgba(0,0,0,0.04);
+ flex-shrink:0;
+ }
+ .ps-toggle-btn {
+ display:flex;align-items:center;gap:0.3rem;
+ padding:0.3rem 0.75rem;border-radius:100px;border:none;
+ cursor:pointer;
+ font-family:'Nunito',sans-serif;font-size:0.72rem;font-weight:800;
+ color:#9ca3af;background:transparent;
+ transition:background 0.15s,color 0.15s,box-shadow 0.15s;
+ white-space:nowrap;
+ }
+ .ps-toggle-btn.active {
+ background:linear-gradient(135deg,#a855f7,#7c3aed);
+ color:white;
+ box-shadow:0 2px 8px rgba(124,58,237,0.25);
+ }
+ .ps-toggle-btn:not(.active):hover { color:#7c3aed;background:#f5f3ff; }
+
+ /* ── Standard grid ── */
+ .ps-grid { display:grid;grid-template-columns:1fr;gap:1rem; }
+ @media(min-width:520px){ .ps-grid { grid-template-columns:1fr 1fr; } }
+ @media(min-width:900px){ .ps-grid { grid-template-columns:repeat(3,1fr); } }
+
+ .ps-card {
+ background:white;border:2.5px solid #f3f4f6;border-radius:22px;
+ overflow:hidden;cursor:pointer;
+ box-shadow:0 4px 14px rgba(0,0,0,0.04);
+ display:flex;flex-direction:column;
+ transition:transform 0.15s ease,box-shadow 0.15s ease,border-color 0.15s ease;
+ animation:psPopIn 0.4s cubic-bezier(0.34,1.56,0.64,1) both;
+ }
+ .ps-card:hover { transform:translateY(-4px);box-shadow:0 14px 32px rgba(0,0,0,0.09);border-color:#e9d5ff; }
+ .ps-card:active { transform:translateY(1px);box-shadow:0 3px 8px rgba(0,0,0,0.06); }
+ .ps-card.locked { opacity:0.8; }
+
+ .ps-card-illo { width:100%;height:90px;flex-shrink:0;position:relative;overflow:hidden; }
+ .ps-card-body { padding:1rem 1.1rem 1rem;display:flex;flex-direction:column;gap:0.55rem;flex:1; }
+ .ps-card-title-row { display:flex;align-items:flex-start;justify-content:space-between;gap:0.5rem; }
+ .ps-card-title { font-size:0.97rem;font-weight:900;color:#1e1b4b;line-height:1.3;flex:1; }
+ .ps-lock-icon { color:#9ca3af;flex-shrink:0;margin-top:1px; }
+ .ps-card-desc {
+ font-family:'Nunito Sans',sans-serif;
+ font-size:0.75rem;font-weight:600;color:#9ca3af;line-height:1.45;
+ display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
+ }
+ .ps-pill-row { display:flex;flex-wrap:wrap;gap:0.4rem; }
+ .ps-pill {
+ display:inline-flex;align-items:center;gap:0.28rem;
+ padding:0.25rem 0.65rem;border-radius:100px;
+ font-size:0.7rem;font-weight:800;border:1.5px solid transparent;white-space:nowrap;
+ }
+ .ps-stats-row { display:flex;flex-wrap:wrap;gap:0.75rem;margin-top:0.1rem; }
+ .ps-stat { display:flex;align-items:center;gap:0.3rem;font-size:0.72rem;font-weight:700;color:#6b7280; }
+ .ps-stat svg { flex-shrink:0; }
+ .ps-tags-row { display:flex;flex-wrap:wrap;gap:0.35rem;margin-top:0.1rem;align-items:center; }
+ .ps-tag { background:#f3f4f6;border-radius:6px;padding:0.18rem 0.5rem;font-size:0.65rem;font-weight:700;color:#6b7280;white-space:nowrap; }
+ .ps-card-footer {
+ display:flex;align-items:center;justify-content:space-between;
+ padding:0.6rem 1.1rem 0.85rem;border-top:1.5px solid #f3f4f6;margin-top:auto;
+ }
+ .ps-author { display:flex;align-items:center;gap:0.4rem;font-size:0.7rem;font-weight:700;color:#9ca3af; }
+ .ps-author-avatar {
+ width:22px;height:22px;border-radius:50%;
+ background:linear-gradient(135deg,#a855f7,#7c3aed);
+ display:flex;align-items:center;justify-content:center;
+ font-size:0.6rem;font-weight:800;color:white;flex-shrink:0;
+ }
+ .ps-cta-arrow { font-size:0.72rem;font-weight:800;color:#7c3aed;display:flex;align-items:center;gap:0.2rem;transition:gap 0.2s ease; }
+ .ps-card:hover .ps-cta-arrow { gap:0.45rem; }
+
+ /* ── Compact list ── */
+ .ps-compact-list { display:flex;flex-direction:column;gap:0.45rem; }
+
+ .ps-compact-card {
+ background:white;border:2px solid #f3f4f6;border-radius:16px;
+ display:flex;align-items:center;gap:0.85rem;
+ padding:0 1rem 0 0;
+ overflow:hidden;cursor:pointer;
+ box-shadow:0 2px 8px rgba(0,0,0,0.04);
+ transition:transform 0.12s ease,box-shadow 0.12s ease,border-color 0.12s ease;
+ animation:psPopIn 0.35s cubic-bezier(0.34,1.56,0.64,1) both;
+ min-height:56px;
+ }
+ .ps-compact-card:hover {
+ transform:translateX(3px);
+ box-shadow:0 6px 20px rgba(0,0,0,0.08);
+ border-color:#e9d5ff;
+ }
+ .ps-compact-card:active { transform:translateX(1px); }
+ .ps-compact-card.locked { opacity:0.75;cursor:default; }
+
+ .ps-compact-bar {
+ width:5px;align-self:stretch;flex-shrink:0;border-radius:0;
+ }
+
+ .ps-compact-main {
+ flex:1;display:flex;flex-direction:column;gap:0.28rem;
+ padding:0.6rem 0;min-width:0;
+ }
+
+ .ps-compact-title {
+ font-size:0.87rem;font-weight:900;color:#1e1b4b;
+ line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
+ display:flex;align-items:center;gap:4px;
+ }
+
+ .ps-compact-meta {
+ display:flex;flex-wrap:wrap;align-items:center;gap:0.28rem;
+ }
+
+ .ps-compact-chip {
+ display:inline-flex;align-items:center;gap:0.2rem;
+ padding:0.16rem 0.45rem;border-radius:100px;
+ font-size:0.63rem;font-weight:800;white-space:nowrap;
+ }
+ .ps-compact-chip-neutral { background:#f3f4f6;color:#6b7280; }
+
+ .ps-compact-start-btn {
+ display:inline-flex;align-items:center;gap:0.3rem;
+ padding:0.38rem 0.85rem;border-radius:100px;border:none;
+ cursor:pointer;
+ font-family:'Nunito',sans-serif;font-size:0.72rem;font-weight:800;color:white;
+ flex-shrink:0;
+ box-shadow:0 3px 0 rgba(0,0,0,0.12);
+ transition:transform 0.1s ease,box-shadow 0.1s ease;
+ }
+ .ps-compact-start-btn:hover { transform:translateY(-1px);box-shadow:0 4px 0 rgba(0,0,0,0.12); }
+ .ps-compact-start-btn:active { transform:translateY(1px);box-shadow:0 1px 0 rgba(0,0,0,0.12); }
+
+ .ps-compact-lock-pill {
+ display:inline-flex;align-items:center;justify-content:center;
+ width:30px;height:30px;border-radius:50%;
+ background:#f3f4f6;color:#9ca3af;flex-shrink:0;
+ }
+
+ /* Skeleton */
+ .ps-skeleton { pointer-events:none; }
+ .ps-skel-block, .ps-skel-line {
+ background:linear-gradient(90deg,#f3f4f6 25%,#e5e7eb 50%,#f3f4f6 75%);
+ background-size:200% 100%;
+ animation:psSkelShimmer 1.4s ease-in-out infinite;
+ border-radius:8px;
+ }
+ @keyframes psSkelShimmer {
+ 0%{background-position:200% 0;}
+ 100%{background-position:-200% 0;}
+ }
+
+ /* Empty */
+ .ps-empty {
+ display:flex;flex-direction:column;align-items:center;
+ gap:0.75rem;padding:3rem 1rem;text-align:center;grid-column:1/-1;
+ }
+ .ps-empty-title { font-size:1rem;font-weight:900;color:#1e1b4b; }
+ .ps-empty-sub { font-family:'Nunito Sans',sans-serif;font-size:0.82rem;font-weight:600;color:#9ca3af; }
+
+ /* Error */
+ .ps-error {
+ background:#fff5f5;border:2px solid #fecaca;border-radius:16px;
+ padding:1rem 1.25rem;font-size:0.85rem;font-weight:700;color:#dc2626;
+ display:flex;align-items:center;gap:0.6rem;
+ }
+`;
+
+/* ─────────────────────────────────────────────
+ Main component
+───────────────────────────────────────────── */
+type ViewMode = "standard" | "compact";
+
+export const PracticeSheetList = () => {
+ const navigate = useNavigate();
+ const [sheets, setSheets] = useState
([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [query, setQuery] = useState("");
+ const [viewMode, setViewMode] = useState("compact");
+
+ useEffect(() => {
+ setLoading(true);
+ const authStorage = localStorage.getItem("auth-storage");
+ if (!authStorage) return;
+ const {
+ state: { token },
+ } = JSON.parse(authStorage);
+ if (!token) return;
+ api
+ .getPracticeSheets(token, 1, 10)
+ .then((data) => {
+ const normalized: PracticeSheet[] = Array.isArray(data)
+ ? data
+ : Array.isArray(data?.data)
+ ? data.data
+ : Array.isArray(data?.results)
+ ? data.results
+ : Array.isArray(data?.practice_sheets)
+ ? data.practice_sheets
+ : [];
+ setSheets(normalized);
+ setError(null);
+ })
+ .catch(() => setError("Couldn't load practice sheets. Please try again."))
+ .finally(() => setLoading(false));
+ }, []);
+
+ const filtered = useMemo(() => {
+ if (!query.trim()) return sheets;
+ const q = query.toLowerCase();
+ return sheets.filter(
+ (s) =>
+ s.title.toLowerCase().includes(q) ||
+ s.description?.toLowerCase().includes(q) ||
+ s.topics?.some((t) => t.name.toLowerCase().includes(q)) ||
+ s.difficulty?.toLowerCase().includes(q),
+ );
+ }, [sheets, query]);
+
+ const handleStart = (sheet: PracticeSheet) => {
+ if (!sheet.is_locked) navigate(`/student/practice/${sheet.id}`);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {DOTS.map((d, i) => (
+
+ ))}
+
+
+ {/* Page heading */}
+
+
+
+ Practice Sheets
+
+
+ Pick your sheet,
+
+ own your score 📄
+
+
+ Curated question sets designed to sharpen specific skills — work
+ through them at your own pace.
+
+
+
+ {/* Search */}
+
+
+ setQuery(e.target.value)}
+ />
+ {query && (
+
+ )}
+
+
+ {/* Toolbar: count + view toggle */}
+ {!loading && !error && (
+
+
+ {query ? (
+ <>
+ {filtered.length} result
+ {filtered.length !== 1 ? "s" : ""} for "{query}"
+ >
+ ) : (
+ <>
+ {sheets.length} sheet
+ {sheets.length !== 1 ? "s" : ""} available
+ >
+ )}
+
+
+
+
+
+
+
+ )}
+
+ {/* Error */}
+ {error && (
+
+ ⚠️ {error}
+
+ )}
+
+ {/* Standard grid */}
+ {viewMode === "standard" && (
+
+ {loading ? (
+ Array.from({ length: 6 }).map((_, i) =>
)
+ ) : filtered.length === 0 ? (
+
+ ) : (
+ filtered.map((sheet, idx) => {
+ const diff = getDiff(sheet.difficulty);
+ const status = getStatus(sheet.user_status);
+ return (
+
handleStart(sheet)}
+ >
+
+
+
+
+
+
{sheet.title}
+ {sheet.is_locked && (
+
+ )}
+
+ {sheet.description && (
+
{sheet.description}
+ )}
+
+
+
+ {diff.label}
+
+
+
+ {status.label}
+
+
+
+ {sheet.questions_count > 0 && (
+
+
+ {sheet.questions_count} Q
+
+ )}
+ {sheet.modules_count > 0 && (
+
+
+ {sheet.modules_count} module
+ {sheet.modules_count !== 1 ? "s" : ""}
+
+ )}
+ {sheet.time_limit > 0 && (
+
+
+ {formatTime(sheet.time_limit)}
+
+ )}
+
+ {sheet.topics?.length > 0 && (
+
+
+ {sheet.topics.slice(0, 4).map((t) => (
+
+ {t.name}
+
+ ))}
+ {sheet.topics.length > 4 && (
+
+ +{sheet.topics.length - 4}
+
+ )}
+
+ )}
+
+
+
+
+ {initials(sheet.created_by?.name ?? "")}
+
+
{sheet.created_by?.name ?? "Unknown"}
+
+ {sheet.is_locked ? (
+
+ 🔒 Locked
+
+ ) : (
+
Start →
+ )}
+
+
+ );
+ })
+ )}
+
+ )}
+
+ {/* Compact list */}
+ {viewMode === "compact" && (
+
+ {loading ? (
+ Array.from({ length: 8 }).map((_, i) => (
+
+ ))
+ ) : filtered.length === 0 ? (
+
+ ) : (
+ filtered.map((sheet, idx) => (
+ handleStart(sheet)}
+ />
+ ))
+ )}
+
+ )}
+
+
+ );
+};