import React from "react"; import { fetchStats, StatsResponse } from "../lib/api"; import { Card, CardContent, CardHeader, CardTitle } from "./components/ui"; import Link from "next/link"; export const dynamic = "force-dynamic"; function formatBytes(bytes: number): string { if (bytes === 0) return "0 B"; const k = 1024; const sizes = ["B", "KB", "MB", "GB", "TB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`; } function formatNumber(n: number): string { return n.toLocaleString("fr-FR"); } // Donut chart via SVG function DonutChart({ data, colors }: { data: { label: string; value: number; color: string }[]; colors?: string[] }) { const total = data.reduce((sum, d) => sum + d.value, 0); if (total === 0) return

No data

; const radius = 40; const circumference = 2 * Math.PI * radius; let offset = 0; return (
{data.map((d, i) => { const pct = d.value / total; const dashLength = pct * circumference; const currentOffset = offset; offset += dashLength; return ( ); })} {formatNumber(total)}
{data.map((d, i) => (
{d.label} {d.value}
))}
); } // Bar chart via pure CSS function BarChart({ data, color = "var(--color-primary)" }: { data: { label: string; value: number }[]; color?: string }) { const max = Math.max(...data.map((d) => d.value), 1); if (data.length === 0) return

No data

; return (
{data.map((d, i) => (
{d.value || ""}
{d.label}
))}
); } // Horizontal progress bar for library breakdown function HorizontalBar({ label, value, max, subLabel, color = "var(--color-primary)" }: { label: string; value: number; max: number; subLabel?: string; color?: string }) { const pct = max > 0 ? (value / max) * 100 : 0; return (
{label} {subLabel || formatNumber(value)}
); } export default async function DashboardPage() { let stats: StatsResponse | null = null; try { stats = await fetchStats(); } catch (e) { console.error("Failed to fetch stats:", e); } if (!stats) { return (

StripStream Backoffice

Unable to load statistics. Make sure the API is running.

); } const { overview, reading_status, by_format, by_language, by_library, top_series, additions_over_time } = stats; const readingColors = ["hsl(220 13% 70%)", "hsl(45 93% 47%)", "hsl(142 60% 45%)"]; const formatColors = [ "hsl(198 78% 37%)", "hsl(142 60% 45%)", "hsl(45 93% 47%)", "hsl(2 72% 48%)", "hsl(280 60% 50%)", "hsl(32 80% 50%)", "hsl(170 60% 45%)", "hsl(220 60% 50%)", ]; const maxLibBooks = Math.max(...by_library.map((l) => l.book_count), 1); return (
{/* Header */}

Dashboard

Overview of your comic collection. Manage your libraries, track your reading progress, and explore your books and series.

{/* Overview stat cards */}
{/* Charts row */}
{/* Reading status donut */} Reading Status {/* By format donut */} By Format ({ label: (f.format || "Unknown").toUpperCase(), value: f.count, color: formatColors[i % formatColors.length], }))} /> {/* By library donut */} By Library ({ label: l.library_name, value: l.book_count, color: formatColors[i % formatColors.length], }))} />
{/* Second row */}
{/* Monthly additions bar chart */} Books Added (Last 12 Months) ({ label: m.month.slice(5), // "MM" from "YYYY-MM" value: m.books_added, }))} color="hsl(198 78% 37%)" /> {/* Top series */} Top Series
{top_series.slice(0, 8).map((s, i) => ( ))} {top_series.length === 0 && (

No series yet

)}
{/* Libraries breakdown */} {by_library.length > 0 && ( Libraries
{by_library.map((lib, i) => (
{lib.library_name} {formatBytes(lib.size_bytes)}
{lib.book_count} books {lib.read_count} read {lib.reading_count} in progress
))}
)} {/* Quick links */}
); } function StatCard({ icon, label, value, color }: { icon: string; label: string; value: string; color: string }) { const icons: Record = { book: , series: , library: , pages: , author: , size: , }; const colorClasses: Record = { primary: "bg-primary/10 text-primary", success: "bg-success/10 text-success", warning: "bg-warning/10 text-warning", }; return (
{icons[icon]}

{value}

{label}

); } function QuickLinks() { const links = [ { href: "/libraries", label: "Libraries", bg: "bg-primary/10", text: "text-primary", hoverBg: "group-hover:bg-primary", hoverText: "group-hover:text-primary-foreground", icon: }, { href: "/books", label: "Books", bg: "bg-success/10", text: "text-success", hoverBg: "group-hover:bg-success", hoverText: "group-hover:text-white", icon: }, { href: "/series", label: "Series", bg: "bg-warning/10", text: "text-warning", hoverBg: "group-hover:bg-warning", hoverText: "group-hover:text-white", icon: }, { href: "/jobs", label: "Jobs", bg: "bg-destructive/10", text: "text-destructive", hoverBg: "group-hover:bg-destructive", hoverText: "group-hover:text-destructive-foreground", icon: }, ]; return (
{links.map((l) => (
{l.icon}
{l.label} ))}
); }