feat(messages): Phase 1 read-only Messages viewer (email client shell)
New admin "Messages" page — a three-pane mail viewer over the mail picpeak already stores, feature-flagged behind `messaging` (default off): - Sidebar account tree: All mail / Customers (hello@) / Accounting (rechnungen@) / Automated (no-reply@), matching the agreed IA. - Automated + All Sent = email_queue (listQueue); Accounting + All Inbox = received_emails (listReceived). Customers folders show an explanatory empty state pending the hello@ mailbox (Phase 2). - Reading pane renders the sent body from rendered_html (migration 119) in a sandboxed iframe; new GET /admin/email/queue/:id returns body + cc + attachment filenames (disk paths never exposed). - Received supplier invoices: envelope + rasterized PDF viewer reusing the accounting inbound blob endpoint, plus "Open in Accounting inbox". - Context toolbar (Reply/Forward/Create Quote-Contract-Gallery-Invoice / Book-as-expense-Re-bill) present but disabled — wired in later phases. Reuses email.service, accounting inbound blob endpoint, RequireFeature + PermissionGate (email.view), Tailwind dark: theming. No schema change.
This commit is contained in:
@@ -22,6 +22,15 @@ export interface EmailQueueListResponse {
|
||||
pagination: { total: number; page: number; pageSize: number; totalPages: number };
|
||||
}
|
||||
|
||||
/** Single sent/queued email including its rendered body — Messages reading pane. */
|
||||
export interface EmailQueueDetail extends EmailQueueItem {
|
||||
/** Exact HTML that was sent (migration 119); null for pre-migration rows. */
|
||||
renderedHtml: string | null;
|
||||
cc: string | null;
|
||||
/** Attachment filenames only — disk paths are never exposed. */
|
||||
attachments: { filename: string; contentType: string | null }[];
|
||||
}
|
||||
|
||||
export interface EmailConfig {
|
||||
smtp_host: string;
|
||||
smtp_port: number;
|
||||
@@ -207,6 +216,12 @@ export const emailService = {
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** Single sent email with its rendered body + attachment filenames. */
|
||||
async getQueueItem(id: number): Promise<EmailQueueDetail> {
|
||||
const response = await api.get<EmailQueueDetail>(`/admin/email/queue/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Get all email templates
|
||||
async getTemplates(): Promise<EmailTemplate[]> {
|
||||
const response = await api.get<EmailTemplate[]>('/admin/email/templates');
|
||||
|
||||
Reference in New Issue
Block a user