feat(ui): replace heatmap with category breakdown + skeletons
Remove dead HeatmapWidget and the category heatmap grid. Replace with an "Emails by Category" ranked horizontal bar list (top 8), each bar a clickable button routing to the category folder via CAT_SLUG. Add Skeleton loading placeholders and richer EmptyState fallbacks to dashboard widgets. Drop the unused AnalyticsApi.heatmap client method and dead heatmap CSS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,8 +6,9 @@ import { AnalyticsApi, LayoutApi, ExportApi, SyncApi } from '../api/client.js';
|
||||
import SyncSplash from '../components/SyncSplash.jsx';
|
||||
import {
|
||||
HealthWidget, StatCard, TopSendersWidget, VolumeWidget,
|
||||
CategoryHeatmapWidget, AttachmentsWidget, StorageWidget
|
||||
CategoryBreakdownWidget, AttachmentsWidget, StorageWidget
|
||||
} from '../components/widgets.jsx';
|
||||
import { Skeleton } from '../components/ui/skeleton.jsx';
|
||||
|
||||
// Default grid geometry; overridden by the user's saved layout.
|
||||
const DEFAULT_LAYOUT = [
|
||||
@@ -17,12 +18,23 @@ const DEFAULT_LAYOUT = [
|
||||
{ i: 'storage', x: 7, y: 0, w: 2, h: 2 },
|
||||
{ i: 'top-senders', x: 3, y: 2, w: 3, h: 5 },
|
||||
{ i: 'volume', x: 6, y: 2, w: 6, h: 4 },
|
||||
{ i: 'category-heatmap', x: 0, y: 5, w: 6, h: 5 },
|
||||
{ i: 'category-breakdown', x: 0, y: 5, w: 6, h: 5 },
|
||||
{ i: 'attachments', x: 6, y: 6, w: 4, h: 4 },
|
||||
];
|
||||
|
||||
const ALL_WIDGETS = DEFAULT_LAYOUT.map((l) => l.i);
|
||||
|
||||
function WidgetSkeleton() {
|
||||
return (
|
||||
<div className="widget">
|
||||
<Skeleton className="mb-3 h-4 w-1/3" />
|
||||
<Skeleton className="mb-2 h-3 w-full" />
|
||||
<Skeleton className="mb-2 h-3 w-5/6" />
|
||||
<Skeleton className="h-3 w-2/3" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Dashboard() {
|
||||
const [data, setData] = useState(null);
|
||||
const [layout, setLayout] = useState(DEFAULT_LAYOUT);
|
||||
@@ -87,15 +99,18 @@ export default function Dashboard() {
|
||||
const visibleLayout = useMemo(() => layout.filter((l) => !hidden.includes(l.i)), [layout, hidden]);
|
||||
|
||||
const render = (key) => {
|
||||
// category-breakdown self-fetches, so it renders regardless of dashboard load state.
|
||||
if (key === 'category-breakdown') return <CategoryBreakdownWidget />;
|
||||
// While the dashboard payload loads, show a skeleton placeholder per widget.
|
||||
if (!data) return <WidgetSkeleton />;
|
||||
switch (key) {
|
||||
case 'inbox-health': return <HealthWidget health={data?.health} />;
|
||||
case 'total-emails': return <StatCard label="Total Emails" value={(data?.totalEmails ?? 0).toLocaleString()} to="/app/folder/allmail" />;
|
||||
case 'unread-emails': return <StatCard label="Unread" value={(data?.unreadEmails ?? 0).toLocaleString()} to="/app/folder/unread" />;
|
||||
case 'storage': return <StorageWidget bytes={data?.storageEstimateBytes} />;
|
||||
case 'top-senders': return <TopSendersWidget senders={data?.topSenders} />;
|
||||
case 'volume': return <VolumeWidget volume={data?.volumeOverTime} />;
|
||||
case 'category-heatmap': return <CategoryHeatmapWidget />;
|
||||
case 'attachments': return <AttachmentsWidget attachments={data?.attachmentBreakdown} />;
|
||||
case 'inbox-health': return <HealthWidget health={data.health} />;
|
||||
case 'total-emails': return <StatCard label="Total Emails" value={(data.totalEmails ?? 0).toLocaleString()} to="/app/folder/allmail" />;
|
||||
case 'unread-emails': return <StatCard label="Unread" value={(data.unreadEmails ?? 0).toLocaleString()} to="/app/folder/unread" />;
|
||||
case 'storage': return <StorageWidget bytes={data.storageEstimateBytes} />;
|
||||
case 'top-senders': return <TopSendersWidget senders={data.topSenders} />;
|
||||
case 'volume': return <VolumeWidget volume={data.volumeOverTime} />;
|
||||
case 'attachments': return <AttachmentsWidget attachments={data.attachmentBreakdown} />;
|
||||
default: return null;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user