import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Bar, Line, Doughnut } from 'react-chartjs-2'; import { AnalyticsApi } from '../api/client.js'; import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, PointElement, LineElement, ArcElement, Tooltip, Legend } from 'chart.js'; ChartJS.register(CategoryScale, LinearScale, BarElement, PointElement, LineElement, ArcElement, Tooltip, Legend); const fmtBytes = (b) => { if (!b) return '0 B'; const u = ['B', 'KB', 'MB', 'GB']; let i = 0; let n = b; while (n >= 1024 && i < u.length - 1) { n /= 1024; i++; } return `${n.toFixed(1)} ${u[i]}`; }; export function StatCard({ label, value, to }) { const navigate = useNavigate(); return (
navigate(to) : undefined} title={to ? `View ${label}` : undefined}>
{value}
{label}
); } export function HealthWidget({ health }) { if (!health) return ; return (

Inbox Health

{health.score}/100
Grade {health.grade}
); } export function TopSendersWidget({ senders }) { const navigate = useNavigate(); if (!senders) return ; return (

Top Senders

{senders.map((s) => ( navigate(`/app/search?q=${encodeURIComponent(`from:${s.address}`)}`)} title={`Search emails from ${s.address}`} > ))}
{s.displayName || s.address} {s.emailCount}
); } export function VolumeWidget({ volume }) { if (!volume) return ; const data = { labels: volume.map((p) => p.day), datasets: [{ label: 'Emails', data: volume.map((p) => p.count), borderColor: '#4f8cff', tension: 0.3 }] }; return (

Email Volume

); } export function HeatmapWidget({ heatmap }) { if (!heatmap) return ; const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; const max = Math.max(1, ...heatmap.map((c) => c.count)); const grid = {}; heatmap.forEach((c) => { grid[`${c.dayOfWeek}-${c.hour}`] = c.count; }); return (

Activity Heatmap

{days.map((d, dow) => (
{d} {Array.from({ length: 24 }, (_, h) => { const v = grid[`${dow}-${h}`] || 0; return ; })}
))}
); } const DOW = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; // Maps EmailCategory enum name → folder slug or search query const CAT_SLUG = { Finance: '/app/folder/finance', Social: '/app/folder/social', Notification: '/app/folder/automated', Promotional: '/app/folder/shopping', Shopping: '/app/folder/shopping', Gaming: '/app/folder/gaming', RideSharing: '/app/folder/ridesharing', FoodDelivery: '/app/folder/food', SeasonalSales: '/app/folder/sales', Wellness: '/app/folder/wellness', Travel: '/app/folder/travel', Subscriptions: '/app/folder/subscriptions', Parcels: '/app/folder/parcels', Recruitment: '/app/folder/recruitment', Events: '/app/folder/events', SecurityAlerts: '/app/folder/security', Healthcare: '/app/folder/healthcare', Education: '/app/folder/education', NewsMedia: '/app/folder/news', PropertyUtilities:'/app/folder/property', Charity: '/app/folder/charity', Government: '/app/folder/government', CryptoInvesting: '/app/folder/crypto', FamilySchool: '/app/folder/family', Spam: '/app/folder/spam', Newsletter: '/app/search?q=newsletter', Personal: '/app/search?q=is:unread', Unknown: '/app/folder/allmail', }; export function CategoryHeatmapWidget() { const [cells, setCells] = useState(null); const navigate = useNavigate(); useEffect(() => { AnalyticsApi.categoryHeatmap().then(setCells).catch(() => setCells([])); }, []); if (!cells) return
Loading…
; if (cells.length === 0) return

Category Heatmap

No data yet — run a sync.
; const categories = [...new Set(cells.map((c) => c.category))].sort(); const max = Math.max(1, ...cells.map((c) => c.count)); const grid = {}; cells.forEach((c) => { grid[`${c.category}-${c.dayOfWeek}`] = c.count; }); return (

Category Heatmap

{DOW.map((d) => {d})}
{categories.map((cat) => { const dest = CAT_SLUG[cat]; return (
navigate(dest) : undefined} >{cat} {DOW.map((_, dow) => { const v = grid[`${cat}-${dow}`] || 0; return {v || ''}; })}
); })}
); } export function AttachmentsWidget({ attachments }) { if (!attachments) return ; const data = { labels: attachments.map((a) => a.mimeBucket), datasets: [{ data: attachments.map((a) => a.totalBytes), backgroundColor: ['#4f8cff', '#6fcf97', '#f2c94c', '#eb5757', '#bb6bd9', '#56ccf2', '#a0a0a0'] }] }; return (

Attachments by Type

Total: {fmtBytes(attachments.reduce((s, a) => s + a.totalBytes, 0))}
); } export function StorageWidget({ bytes }) { return ; } function Empty() { return
No data yet — run a sync.
; }