"use client"; import { useMemo } from "react"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Progress } from "@/components/ui/progress"; import { Skeleton } from "@/components/ui/skeleton"; import { api } from "@/lib/trpc"; import { Archive, BarChart3, BookOpen, Clock, Database, FileText, Globe, Hash, Heart, Highlighter, Image, Link, List, TrendingUp, } from "lucide-react"; function formatBytes(bytes: number): string { if (bytes === 0) return "0 Bytes"; const k = 1024; const sizes = ["Bytes", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; } function formatNumber(num: number): string { if (num >= 1000000) { return (num / 1000000).toFixed(1) + "M"; } if (num >= 1000) { return (num / 1000).toFixed(1) + "K"; } return num.toString(); } const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; const hourLabels = Array.from({ length: 24 }, (_, i) => i === 0 ? "12 AM" : i < 12 ? `${i} AM` : i === 12 ? "12 PM" : `${i - 12} PM`, ); function SimpleBarChart({ data, maxValue, labels, }: { data: number[]; maxValue: number; labels: string[]; }) { return (
{data.map((value, index) => (
{labels[index]}
0 ? (value / maxValue) * 100 : 0}%`, }} />
{value}
))}
); } function StatCard({ title, value, icon, description, }: { title: string; value: string | number; icon: React.ReactNode; description?: string; }) { return ( {title} {icon}
{value}
{description && (

{description}

)}
); } export default function StatsPage() { const { data: stats, isLoading } = api.users.stats.useQuery(); const maxHourlyActivity = useMemo(() => { if (!stats) return 0; return Math.max( ...stats.bookmarkingActivity.byHour.map( (h: { hour: number; count: number }) => h.count, ), ); }, [stats]); const maxDailyActivity = useMemo(() => { if (!stats) return 0; return Math.max( ...stats.bookmarkingActivity.byDayOfWeek.map( (d: { day: number; count: number }) => d.count, ), ); }, [stats]); if (isLoading) { return (

Usage Statistics

Insights into your bookmarking habits and collection

{Array.from({ length: 8 }).map((_, i) => ( ))}
); } if (!stats) { return (

Failed to load statistics

); } return (

Usage Statistics

Insights into your bookmarking habits and collection

{/* Overview Stats */}
} description="All saved items" /> } description="Starred bookmarks" /> } description="Archived items" /> } description="Unique tags created" /> } description="Bookmark collections" /> } description="Text highlights" /> } description="Total asset storage" /> } description="Bookmarks added" />
{/* Bookmark Types */} Bookmark Types
Links
{stats.bookmarksByType.link}
0 ? (stats.bookmarksByType.link / stats.numBookmarks) * 100 : 0 } className="h-2" />
Text Notes
{stats.bookmarksByType.text}
0 ? (stats.bookmarksByType.text / stats.numBookmarks) * 100 : 0 } className="h-2" />
Assets
{stats.bookmarksByType.asset}
0 ? (stats.bookmarksByType.asset / stats.numBookmarks) * 100 : 0 } className="h-2" />
{/* Recent Activity */} Recent Activity
{stats.bookmarkingActivity.thisWeek}
This Week
{stats.bookmarkingActivity.thisMonth}
This Month
{stats.bookmarkingActivity.thisYear}
This Year
{/* Top Domains */} Top Domains {stats.topDomains.length > 0 ? (
{stats.topDomains .slice(0, 8) .map( ( domain: { domain: string; count: number }, index: number, ) => (
{index + 1}
{domain.domain}
{domain.count}
), )}
) : (

No domains found

)}
{/* Top Tags */} Most Used Tags {stats.tagUsage.length > 0 ? (
{stats.tagUsage .slice(0, 8) .map( (tag: { name: string; count: number }, index: number) => (
{index + 1}
{tag.name}
{tag.count}
), )}
) : (

No tags found

)}
{/* Activity Patterns */}
{/* Hourly Activity */} Activity by Hour h.count, )} maxValue={maxHourlyActivity} labels={hourLabels} /> {/* Daily Activity */} Activity by Day d.count, )} maxValue={maxDailyActivity} labels={dayNames} />
{/* Asset Storage */} {stats.assetsByType.length > 0 && ( Storage Breakdown
{stats.assetsByType.map( (asset: { type: string; count: number; totalSize: number }) => (
{asset.type.replace(/([A-Z])/g, " $1").trim()} {asset.count}
{formatBytes(asset.totalSize)}
0 ? (asset.totalSize / stats.totalAssetSize) * 100 : 0 } className="h-2" />
), )}
)}
); }