From 80c2167b8915181893055aaf9f925d7d99be42a7 Mon Sep 17 00:00:00 2001 From: cesnimda Date: Tue, 30 Jun 2026 20:20:09 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20dashboard=20drillthrough=20=E2=80=94=20?= =?UTF-8?q?widgets=20navigate=20to=20filtered=20views?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Total Emails stat card -> /app/folder/allmail - Unread stat card -> /app/folder/unread - Top Senders rows -> /app/search?q=from:{address} - Category heatmap labels -> matching folder or search (Finance, Social, Notification, Promotional, Spam, Newsletter, Personal) Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/components/widgets.jsx | 52 ++++++++++++++++++++++------- frontend/src/pages/Dashboard.jsx | 4 +-- frontend/src/styles.css | 6 ++++ 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/widgets.jsx b/frontend/src/components/widgets.jsx index 5c4e6ad..7b79e88 100644 --- a/frontend/src/components/widgets.jsx +++ b/frontend/src/components/widgets.jsx @@ -1,4 +1,5 @@ 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 { @@ -15,9 +16,10 @@ const fmtBytes = (b) => { return `${n.toFixed(1)} ${u[i]}`; }; -export function StatCard({ label, value }) { +export function StatCard({ label, value, to }) { + const navigate = useNavigate(); return ( -
+
navigate(to) : undefined} title={to ? `View ${label}` : undefined}>
{value}
{label}
@@ -37,6 +39,7 @@ export function HealthWidget({ health }) { } export function TopSendersWidget({ senders }) { + const navigate = useNavigate(); if (!senders) return ; return (
@@ -44,7 +47,12 @@ export function TopSendersWidget({ senders }) { {senders.map((s) => ( - + navigate(`/app/search?q=${encodeURIComponent(`from:${s.address}`)}`)} + title={`Search emails from ${s.address}`} + > @@ -95,8 +103,21 @@ export function HeatmapWidget({ heatmap }) { 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', + 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…
; @@ -115,15 +136,22 @@ export function CategoryHeatmapWidget() { {DOW.map((d) => {d})} - {categories.map((cat) => ( -
- {cat} - {DOW.map((_, dow) => { - const v = grid[`${cat}-${dow}`] || 0; - return {v || ''}; - })} -
- ))} + {categories.map((cat) => { + const dest = CAT_SLUG[cat]; + return ( +
+ navigate(dest) : undefined} + >{cat} + {DOW.map((_, dow) => { + const v = grid[`${cat}-${dow}`] || 0; + return {v || ''}; + })} +
+ ); + })} ); diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index 7d7aa09..83ffeda 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -89,8 +89,8 @@ export default function Dashboard() { const render = (key) => { switch (key) { case 'inbox-health': return ; - case 'total-emails': return ; - case 'unread-emails': return ; + case 'total-emails': return ; + case 'unread-emails': return ; case 'storage': return ; case 'top-senders': return ; case 'volume': return ; diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 111da29..e6d95c0 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -111,6 +111,12 @@ button:disabled { opacity: 0.5; cursor: default; } .grid-item { background: var(--panel); border: 1px solid #2c3550; border-radius: 10px; overflow: hidden; } .widget { padding: 14px; height: 100%; display: flex; flex-direction: column; } .widget h3 { margin: 0 0 10px; font-size: 14px; cursor: move; } +.widget--link { cursor: pointer; } +.widget--link:hover { border-color: var(--accent); } +.mini-row--link { cursor: pointer; } +.mini-row--link:hover td { color: var(--accent); } +.chm-label--link { cursor: pointer; text-decoration: underline dotted; } +.chm-label--link:hover { color: var(--accent); } .widget canvas { flex: 1; min-height: 0; } .stat { align-items: flex-start; justify-content: center; }
{s.displayName || s.address} {s.emailCount}