"use client"; import Link from "next/link"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { apiFetch, apiUrl } from "@/lib/api"; import { formatRelativeTime } from "@/lib/relative-time"; interface RecentBook { id: string; title: string; status: string; chapter_count: number; page_count: number; updated_at: number; } const STATUS_DOT: Record = { ready: "bg-emerald-400", partial: "bg-amber-400", generating: "bg-sky-400", planning: "bg-sky-400", pending: "bg-[var(--muted-foreground)]/30", draft: "bg-[var(--muted-foreground)]/30", spine_ready: "bg-violet-400", error: "bg-rose-400", }; interface BookRecentProps { collapsed?: boolean; limit?: number; } export function BookRecent({ collapsed = false, limit = 4 }: BookRecentProps) { const { i18n } = useTranslation(); const [books, setBooks] = useState([]); useEffect(() => { let cancelled = false; (async () => { try { const res = await apiFetch(apiUrl("/api/v1/book/books")); if (!res.ok) return; const data = await res.json(); const items: RecentBook[] = Array.isArray(data?.books) ? data.books : []; items.sort( (a, b) => (Number(b.updated_at) || 0) - (Number(a.updated_at) || 0), ); if (!cancelled) setBooks(items.slice(0, limit)); } catch { /* ignore */ } })(); return () => { cancelled = true; }; }, [limit]); if (books.length === 0) return null; if (collapsed) return null; return (
{books.map((book) => { const dot = STATUS_DOT[book.status] || "bg-[var(--muted-foreground)]/30"; return ( {book.title || "Untitled book"} {formatRelativeTime(Number(book.updated_at) || 0, i18n.language)} ); })}
); }