feat: Smart Folders sidebar, category heatmap, sidebar counts API
- Replace plain activity heatmap with CategoryHeatmapWidget on dashboard - Add collapsible Smart Folders sidebar with Favorites, Mailbox, Smart Folders sections - Favorites: customisable pinned shortcuts (default: Inbox, Unread, Large, Old, Read Later); pin/unpin smart folders via buttons; persists to localStorage - Mailbox section: 11 Gmail mailbox items with icons and live count badges - Smart Folders section: 10 category folders sorted by count desc - Live count badges via new GET /api/v1/analytics/sidebar-counts endpoint - Backend: SidebarCountsDto, GetSidebarCountsAsync with label lookups for SENT/DRAFT/SPAM, size/age filters, EmailCategory mapping - Sidebar collapses to icon-only; section states persist to localStorage Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
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
|
||||
@@ -91,6 +93,42 @@ export function HeatmapWidget({ heatmap }) {
|
||||
);
|
||||
}
|
||||
|
||||
const DOW = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||
|
||||
export function CategoryHeatmapWidget() {
|
||||
const [cells, setCells] = useState(null);
|
||||
useEffect(() => { AnalyticsApi.categoryHeatmap().then(setCells).catch(() => setCells([])); }, []);
|
||||
|
||||
if (!cells) return <div className="widget"><div className="muted">Loading…</div></div>;
|
||||
if (cells.length === 0) return <div className="widget"><h3>Category Heatmap</h3><div className="muted">No data yet — run a sync.</div></div>;
|
||||
|
||||
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 (
|
||||
<div className="widget">
|
||||
<h3>Category Heatmap</h3>
|
||||
<div className="cat-heatmap">
|
||||
<div className="chm-row chm-head">
|
||||
<span className="chm-label" />
|
||||
{DOW.map((d) => <span key={d} className="chm-col">{d}</span>)}
|
||||
</div>
|
||||
{categories.map((cat) => (
|
||||
<div className="chm-row" key={cat}>
|
||||
<span className="chm-label" title={cat}>{cat}</span>
|
||||
{DOW.map((_, dow) => {
|
||||
const v = grid[`${cat}-${dow}`] || 0;
|
||||
return <span key={dow} className="chm-cell" style={{ opacity: 0.12 + 0.88 * (v / max) }} title={`${cat} · ${DOW[dow]} — ${v}`}>{v || ''}</span>;
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AttachmentsWidget({ attachments }) {
|
||||
if (!attachments) return <Empty />;
|
||||
const data = {
|
||||
|
||||
Reference in New Issue
Block a user