chore: init project

This commit is contained in:
cesnimda
2026-06-30 15:53:32 +02:00
commit f43ef5f945
94 changed files with 4405 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
import { Bar, Line, Doughnut } from 'react-chartjs-2';
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 }) {
return (
<div className="widget stat">
<div className="stat-value">{value}</div>
<div className="stat-label">{label}</div>
</div>
);
}
export function HealthWidget({ health }) {
if (!health) return <Empty />;
return (
<div className="widget">
<h3>Inbox Health</h3>
<div className={`health-score grade-${health.grade}`}>{health.score}<span>/100</span></div>
<div className="grade">Grade {health.grade}</div>
<ul className="recs">{health.recommendations.map((r, i) => <li key={i}>{r}</li>)}</ul>
</div>
);
}
export function TopSendersWidget({ senders }) {
if (!senders) return <Empty />;
return (
<div className="widget">
<h3>Top Senders</h3>
<table className="mini">
<tbody>
{senders.map((s) => (
<tr key={s.senderId}>
<td title={s.address}>{s.displayName || s.address}</td>
<td className="num">{s.emailCount}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
export function VolumeWidget({ volume }) {
if (!volume) return <Empty />;
const data = {
labels: volume.map((p) => p.day),
datasets: [{ label: 'Emails', data: volume.map((p) => p.count), borderColor: '#4f8cff', tension: 0.3 }]
};
return (
<div className="widget">
<h3>Email Volume</h3>
<Line data={data} options={{ plugins: { legend: { display: false } }, maintainAspectRatio: false }} />
</div>
);
}
export function HeatmapWidget({ heatmap }) {
if (!heatmap) return <Empty />;
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 (
<div className="widget">
<h3>Activity Heatmap</h3>
<div className="heatmap">
{days.map((d, dow) => (
<div className="hm-row" key={dow}>
<span className="hm-day">{d}</span>
{Array.from({ length: 24 }, (_, h) => {
const v = grid[`${dow}-${h}`] || 0;
return <span key={h} className="hm-cell" style={{ opacity: 0.1 + 0.9 * (v / max) }} title={`${d} ${h}:00 — ${v}`} />;
})}
</div>
))}
</div>
</div>
);
}
export function AttachmentsWidget({ attachments }) {
if (!attachments) return <Empty />;
const data = {
labels: attachments.map((a) => a.mimeBucket),
datasets: [{ data: attachments.map((a) => a.totalBytes), backgroundColor: ['#4f8cff', '#6fcf97', '#f2c94c', '#eb5757', '#bb6bd9', '#56ccf2', '#a0a0a0'] }]
};
return (
<div className="widget">
<h3>Attachments by Type</h3>
<Doughnut data={data} options={{ plugins: { legend: { position: 'right' } }, maintainAspectRatio: false }} />
<div className="muted">Total: {fmtBytes(attachments.reduce((s, a) => s + a.totalBytes, 0))}</div>
</div>
);
}
export function StorageWidget({ bytes }) {
return <StatCard label="Estimated Storage" value={fmtBytes(bytes)} />;
}
function Empty() { return <div className="widget"><div className="muted">No data yet run a sync.</div></div>; }