import { faChevronLeft, faCirclePlay, faFile, faFileImport, faLayerGroup, faPen, faPlus, faTrash, } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useAtomValue, useSetAtom } from "jotai"; import { Suspense, useMemo, useState, useTransition } from "react"; import { Link, useParams } from "wouter"; import { type Card, cardsByDeckAtomFamily, deckByIdAtomFamily } from "../atoms"; import { CreateNoteModal } from "../components/CreateNoteModal"; import { DeleteCardModal } from "../components/DeleteCardModal"; import { DeleteNoteModal } from "../components/DeleteNoteModal"; import { EditCardModal } from "../components/EditCardModal"; import { EditNoteModal } from "../components/EditNoteModal"; import { ErrorBoundary } from "../components/ErrorBoundary"; import { ImportNotesModal } from "../components/ImportNotesModal"; import { LoadingSpinner } from "../components/LoadingSpinner"; /** Combined type for display: note group */ type CardDisplayItem = { type: "note"; noteId: string; cards: Card[] }; const CardStateLabels: Record = { 0: "New", 1: "Learning", 2: "Review", 3: "Relearning", }; const CardStateColors: Record = { 0: "bg-info/10 text-info", 1: "bg-warning/10 text-warning", 2: "bg-success/10 text-success", 3: "bg-error/10 text-error", }; /** Component for displaying a group of cards from the same note */ function NoteGroupCard({ noteId, cards, index, onEditNote, onDeleteNote, }: { noteId: string; cards: Card[]; index: number; onEditNote: () => void; onDeleteNote: () => void; }) { // Use the first card's front/back as preview (normal card takes precedence) const previewCard = cards.find((c) => !c.isReversed) ?? cards[0]; if (!previewCard) return null; return (
{/* Note Header */}
{/* Note Content Preview */}
Front

{previewCard.front}

Back

{previewCard.back}

{/* Cards within this note */}
{cards.map((card) => (
{CardStateLabels[card.state] || "Unknown"} {card.isReversed ? ( Reversed ) : ( Normal )} {card.reps} reviews {card.lapses > 0 && ( {card.lapses} lapses )}
))}
); } function DeckHeader({ deckId }: { deckId: string }) { const deck = useAtomValue(deckByIdAtomFamily(deckId)); return (

{deck.name}

{deck.description &&

{deck.description}

}
); } function CardList({ deckId, onEditNote, onDeleteNote, onCreateNote, }: { deckId: string; onEditNote: (noteId: string) => void; onDeleteNote: (noteId: string) => void; onCreateNote: () => void; }) { const cards = useAtomValue(cardsByDeckAtomFamily(deckId)); // Group cards by note for display const displayItems = useMemo((): CardDisplayItem[] => { const noteGroups = new Map(); for (const card of cards) { const existing = noteGroups.get(card.noteId); if (existing) { existing.push(card); } else { noteGroups.set(card.noteId, [card]); } } // Sort note groups by earliest card creation (newest first) const sortedNoteGroups = Array.from(noteGroups.entries()).sort( ([, cardsA], [, cardsB]) => { const minA = Math.min( ...cardsA.map((c) => new Date(c.createdAt).getTime()), ); const minB = Math.min( ...cardsB.map((c) => new Date(c.createdAt).getTime()), ); return minB - minA; // Newest first }, ); const items: CardDisplayItem[] = []; for (const [noteId, noteCards] of sortedNoteGroups) { // Sort cards within group: normal first, then reversed noteCards.sort((a, b) => { if (a.isReversed === b.isReversed) return 0; return a.isReversed ? 1 : -1; }); items.push({ type: "note", noteId, cards: noteCards }); } return items; }, [cards]); if (cards.length === 0) { return (

No cards yet

Add notes to start studying

); } return (
{displayItems.map((item, index) => ( onEditNote(item.noteId)} onDeleteNote={() => onDeleteNote(item.noteId)} /> ))}
); } function DeckContent({ deckId, onCreateNote, onImportNotes, onEditNote, onDeleteNote, }: { deckId: string; onCreateNote: () => void; onImportNotes: () => void; onEditNote: (noteId: string) => void; onDeleteNote: (noteId: string) => void; }) { const cards = useAtomValue(cardsByDeckAtomFamily(deckId)); return (
{/* Deck Header */} }> {/* Study Button */}
{/* Cards Section */}

Cards ({cards.length})

{/* Card List */}
); } export function DeckDetailPage() { const { deckId } = useParams<{ deckId: string }>(); const [, startTransition] = useTransition(); const reloadCards = useSetAtom(cardsByDeckAtomFamily(deckId || "")); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [isImportModalOpen, setIsImportModalOpen] = useState(false); const [editingCard, setEditingCard] = useState(null); const [editingNoteId, setEditingNoteId] = useState(null); const [deletingCard, setDeletingCard] = useState(null); const [deletingNoteId, setDeletingNoteId] = useState(null); const handleCardMutation = () => { startTransition(() => { reloadCards(); }); }; if (!deckId) { return (

Invalid deck ID

Back to decks
); } return (
{/* Header */}
{/* Main Content */}
}> setIsCreateModalOpen(true)} onImportNotes={() => setIsImportModalOpen(true)} onEditNote={setEditingNoteId} onDeleteNote={setDeletingNoteId} />
{/* Modals */} setIsCreateModalOpen(false)} onNoteCreated={handleCardMutation} /> setIsImportModalOpen(false)} onImportComplete={handleCardMutation} /> setEditingCard(null)} onCardUpdated={handleCardMutation} /> setEditingNoteId(null)} onNoteUpdated={handleCardMutation} /> setDeletingCard(null)} onCardDeleted={handleCardMutation} /> setDeletingNoteId(null)} onNoteDeleted={handleCardMutation} />
); }