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}
{health.recommendations.map((r, i) => - {r}
)}
);
}
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 ;
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.
; }