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:
+104
-1
@@ -19,7 +19,73 @@ body { margin: 0; font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-
|
||||
.spacer { flex: 1; }
|
||||
.user { color: var(--muted); font-size: 13px; }
|
||||
|
||||
main { padding: 20px; }
|
||||
/* ── App body (sidebar + main) ── */
|
||||
.app-body { display: flex; min-height: calc(100vh - 53px); }
|
||||
|
||||
.sidebar {
|
||||
width: 216px; flex-shrink: 0;
|
||||
background: var(--panel); border-right: 1px solid #2c3550;
|
||||
display: flex; flex-direction: column; overflow-y: auto;
|
||||
transition: width 0.2s ease;
|
||||
}
|
||||
.sidebar--collapsed { width: 48px; }
|
||||
|
||||
.sidebar-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 12px 8px 10px;
|
||||
border-bottom: 1px solid #2c3550;
|
||||
min-height: 42px; flex-shrink: 0;
|
||||
}
|
||||
.sidebar-brand { font-size: 12px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--muted); padding-left: 6px; }
|
||||
.sidebar-toggle { padding: 2px 6px; font-size: 17px; line-height: 1; color: var(--muted); flex-shrink: 0; }
|
||||
.sidebar-toggle:hover { color: var(--text); }
|
||||
|
||||
/* Section headings */
|
||||
.section-head {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
width: 100%; background: none; border: none; border-bottom: 1px solid #2c3550;
|
||||
padding: 7px 10px 7px 12px; cursor: pointer;
|
||||
color: var(--muted); font-size: 10px; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase;
|
||||
}
|
||||
.section-head:hover { color: var(--text); }
|
||||
.section-title { flex: 1; text-align: left; white-space: nowrap; overflow: hidden; }
|
||||
.section-arrow { font-size: 13px; transform: rotate(90deg); display: inline-block; transition: transform 0.15s; }
|
||||
.section-arrow--closed { transform: rotate(0deg); }
|
||||
|
||||
/* Folder items */
|
||||
.folder-list { list-style: none; margin: 0; padding: 2px 0; }
|
||||
.folder-item {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
padding: 6px 10px 6px 12px;
|
||||
color: var(--muted); text-decoration: none; font-size: 13px;
|
||||
border-left: 3px solid transparent;
|
||||
white-space: nowrap; overflow: hidden;
|
||||
transition: background 0.1s, color 0.1s;
|
||||
}
|
||||
.folder-item:hover { background: var(--panel-2); color: var(--text); }
|
||||
.folder-item:hover .fav-pin { opacity: 1; }
|
||||
.folder-item--active { border-left-color: var(--accent); color: var(--text); background: var(--panel-2); }
|
||||
.folder-icon { font-size: 14px; flex-shrink: 0; }
|
||||
.folder-label { flex: 1; overflow: hidden; text-overflow: ellipsis; }
|
||||
.folder-spacer { flex: 1; }
|
||||
.folder-badge {
|
||||
font-size: 10px; font-weight: 600; color: var(--muted);
|
||||
background: var(--panel-2); border-radius: 8px; padding: 1px 5px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.folder-item--active .folder-badge { background: #2c3550; color: var(--accent); }
|
||||
|
||||
/* Favorite pin/unpin button */
|
||||
.fav-pin {
|
||||
background: none; border: none; padding: 0 2px; cursor: pointer;
|
||||
font-size: 12px; color: var(--muted); opacity: 0;
|
||||
transition: opacity 0.1s, color 0.1s; flex-shrink: 0; line-height: 1;
|
||||
}
|
||||
.fav-pin:hover { color: var(--accent); }
|
||||
.fav-pin--remove { opacity: 1; color: var(--danger); }
|
||||
.fav-pin--remove:hover { color: #ff7070; }
|
||||
|
||||
main { padding: 20px; flex: 1; min-width: 0; }
|
||||
|
||||
button, .btn { background: var(--accent); color: #fff; border: none; border-radius: 6px; padding: 8px 14px; cursor: pointer; text-decoration: none; font-size: 14px; }
|
||||
button.danger, .btn.danger { background: var(--danger); }
|
||||
@@ -54,6 +120,43 @@ table.grid th, table.grid td { padding: 8px; border-bottom: 1px solid #2c3550; t
|
||||
.hm-day { width: 30px; font-size: 10px; color: var(--muted); }
|
||||
.hm-cell { width: 10px; height: 10px; background: var(--accent); border-radius: 2px; }
|
||||
|
||||
/* Category heatmap */
|
||||
.cat-heatmap { display: flex; flex-direction: column; gap: 3px; overflow: auto; }
|
||||
.chm-row { display: grid; grid-template-columns: 92px repeat(7, 1fr); gap: 3px; align-items: stretch; }
|
||||
.chm-head .chm-col { font-size: 10px; color: var(--muted); text-align: center; }
|
||||
.chm-label { font-size: 11px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.chm-cell { background: var(--accent); border-radius: 3px; min-height: 22px; display: flex; align-items: center; justify-content: center; font-size: 10px; color: #fff; }
|
||||
|
||||
/* react-grid-layout resize handle — make it clearly visible on the dark theme */
|
||||
.react-resizable-handle {
|
||||
opacity: 0; transition: opacity 0.15s;
|
||||
}
|
||||
.grid-item:hover .react-resizable-handle { opacity: 1; }
|
||||
.react-resizable-handle::after {
|
||||
content: ''; position: absolute; right: 4px; bottom: 4px;
|
||||
width: 9px; height: 9px;
|
||||
border-right: 2px solid var(--accent); border-bottom: 2px solid var(--accent);
|
||||
border-bottom-right-radius: 2px;
|
||||
}
|
||||
.react-grid-item.react-grid-placeholder { background: var(--accent); opacity: 0.25; border-radius: 10px; }
|
||||
|
||||
/* Senders by volume */
|
||||
.senders-page { max-width: 1100px; }
|
||||
.tier { margin-top: 22px; }
|
||||
.tier-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
|
||||
.tier-head h3 { margin: 0; font-size: 16px; }
|
||||
.tier-dot { width: 12px; height: 12px; border-radius: 50%; }
|
||||
.tier-count { color: var(--muted); font-size: 12px; }
|
||||
.sender-blocks { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 10px; }
|
||||
.sender-block { background: var(--panel); border: 1px solid #2c3550; border-top: 3px solid var(--accent); border-radius: 8px; padding: 10px 12px; }
|
||||
.sb-name { font-weight: 600; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.sb-domain { color: var(--muted); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-bottom: 6px; }
|
||||
.sb-stats { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
|
||||
.sb-count { font-size: 20px; font-weight: 700; }
|
||||
.sb-unread { font-size: 11px; color: var(--accent); }
|
||||
.sb-unsub { font-size: 10px; color: var(--muted); border: 1px solid #36405c; border-radius: 4px; padding: 0 4px; }
|
||||
.tier-more { margin-top: 8px; }
|
||||
|
||||
.page { max-width: 900px; }
|
||||
.form-row { display: flex; gap: 10px; margin: 14px 0; }
|
||||
.form-row input { flex: 1; }
|
||||
|
||||
Reference in New Issue
Block a user