feat: folder view — clicking sidebar items shows filtered email list

- New FolderView page at /app/folder/:slug renders a paginated email table
- Sidebar links now route to their respective folder views
- Extended SearchRequestDto with IsInInbox, IsStarred, IsTrashed, GmailLabel, Category, MinSizeBytes
- SearchService applies the new filters; GmailLabel does a label-id lookup (SENT/DRAFT/SPAM)
- Frontend folderToRequest() maps every slug to the correct search payload
- Email rows show sender, subject+snippet, attachment icon, size, date; bold for unread
- Pagination controls for large folders

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
cesnimda
2026-06-30 19:59:01 +02:00
parent ca02f40841
commit 20d51b2c15
6 changed files with 218 additions and 1 deletions
+34
View File
@@ -62,6 +62,40 @@ export const LayoutApi = {
save: (layout) => api.put('/widgetlayout', layout)
};
export const SearchApi = {
folder: (slug, page = 1, pageSize = 50) =>
api.post('/search', folderToRequest(slug, page, pageSize)).then((r) => r.data),
};
function folderToRequest(slug, page, pageSize) {
const base = { page, pageSize };
switch (slug) {
// ── Mailbox ──
case 'inbox': return { ...base, isInInbox: true, isTrashed: false };
case 'allmail': return { ...base };
case 'unread': return { ...base, isUnread: true };
case 'starred': return { ...base, isStarred: true };
case 'sent': return { ...base, gmailLabel: 'SENT' };
case 'drafts': return { ...base, gmailLabel: 'DRAFT' };
case 'archive': return { ...base, isInInbox: false, isTrashed: false };
case 'spam': return { ...base, gmailLabel: 'SPAM' };
case 'trash': return { ...base, isTrashed: true };
// ── Special filters ──
case 'large': return { ...base, minSizeBytes: 5_000_000 };
case 'old': {
const d = new Date(); d.setFullYear(d.getFullYear() - 1);
return { ...base, to: d.toISOString().slice(0, 10) };
}
// ── Smart folders ──
case 'automated': return { ...base, category: 'Notification' };
case 'finance': return { ...base, category: 'Finance' };
case 'social': return { ...base, category: 'Social' };
case 'shopping': return { ...base, category: 'Promotional' };
case 'noreply': return { ...base, query: 'from:noreply' };
default: return { ...base };
}
}
export const ExportApi = {
reportUrl: (format) => `/api/v1/export/report?format=${format}`
};