import { DeesElement, property, html, customElement, type TemplateResult, css, state, cssManager } from '@design.estate/dees-element'; import * as appstate from '../appstate.js'; declare global { interface HTMLElementTagNameMap { 'ops-view-emails': OpsViewEmails; } } interface IEmail { id: string; from: string; to: string[]; cc?: string[]; bcc?: string[]; subject: string; body: string; html?: string; attachments?: Array<{ filename: string; size: number; contentType: string; }>; date: number; read: boolean; folder: 'inbox' | 'sent' | 'draft' | 'trash'; flags?: string[]; messageId?: string; inReplyTo?: string; } @customElement('ops-view-emails') export class OpsViewEmails extends DeesElement { @state() private selectedFolder: 'inbox' | 'sent' | 'draft' | 'trash' = 'inbox'; @state() private emails: IEmail[] = []; @state() private selectedEmail: IEmail | null = null; @state() private showCompose = false; @state() private isLoading = false; @state() private searchTerm = ''; constructor() { super(); this.loadEmails(); } public static styles = [ cssManager.defaultStyles, css` :host { display: block; height: 100%; padding: 24px; } .emailContainer { display: grid; grid-template-columns: 250px 1fr; gap: 24px; height: calc(100vh - 200px); } .sidebar { background: white; border: 1px solid #e9ecef; border-radius: 8px; padding: 16px; display: flex; flex-direction: column; gap: 16px; } .folderList { display: flex; flex-direction: column; gap: 8px; } .folderItem { display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-radius: 6px; cursor: pointer; transition: all 0.2s; position: relative; } .folderItem:hover { background: #f5f5f5; } .folderItem.selected { background: #e3f2fd; color: #1976d2; } .folderIcon { font-size: 18px; } .folderLabel { flex: 1; font-weight: 500; } .folderCount { background: #e0e0e0; color: #666; padding: 2px 8px; border-radius: 12px; font-size: 12px; font-weight: 600; } .folderItem.selected .folderCount { background: #1976d2; color: white; } .mainContent { background: white; border: 1px solid #e9ecef; border-radius: 8px; overflow: hidden; display: flex; flex-direction: column; } .emailToolbar { display: flex; align-items: center; gap: 16px; padding: 16px; border-bottom: 1px solid #e9ecef; background: #fafafa; } .searchBox { flex: 1; max-width: 400px; } .emailList { flex: 1; overflow-y: auto; } .emailPreview { background: white; border: 1px solid #e9ecef; border-radius: 8px; overflow: hidden; } .emailHeader { padding: 20px; border-bottom: 1px solid #e9ecef; background: #fafafa; } .emailSubject { font-size: 20px; font-weight: 600; margin-bottom: 12px; } .emailMeta { display: flex; gap: 16px; font-size: 14px; color: #666; } .emailBody { padding: 20px; max-height: 500px; overflow-y: auto; } .emailActions { display: flex; gap: 8px; padding: 16px; border-top: 1px solid #e9ecef; background: #fafafa; } .composeModal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; } .composeContent { background: white; border-radius: 8px; width: 90%; max-width: 800px; max-height: 90vh; overflow: hidden; display: flex; flex-direction: column; } .composeHeader { padding: 20px; border-bottom: 1px solid #e9ecef; display: flex; align-items: center; justify-content: space-between; } .composeTitle { font-size: 20px; font-weight: 600; } .composeForm { flex: 1; overflow-y: auto; padding: 20px; } .composeActions { padding: 20px; border-top: 1px solid #e9ecef; display: flex; gap: 12px; justify-content: flex-end; } .emptyState { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #999; } .emptyIcon { font-size: 64px; margin-bottom: 16px; opacity: 0.3; } .emptyText { font-size: 18px; } `, ]; public render() { return html` Emails
this.searchTerm = (e.target as any).value} > this.refreshEmails()}> ${this.isLoading ? html`` : html``} this.markAllAsRead()}> Mark all read
${this.selectedEmail ? this.renderEmailPreview() : this.renderEmailList()}
${this.showCompose ? this.renderComposeModal() : ''} `; } private renderFolderItem(folder: string, icon: string, label: string, count: number) { return html`
this.selectFolder(folder as any)} > ${label} ${count > 0 ? html`${count}` : ''}
`; } private renderEmailList() { const filteredEmails = this.getFilteredEmails(); if (filteredEmails.length === 0) { return html`
No emails in ${this.selectedFolder}
`; } return html`
({ 'Status': html``, From: email.from, Subject: html`${email.subject}`, Date: this.formatDate(email.date), 'Attach': html` ${email.attachments?.length ? html`` : ''} `, })} .dataActions=${[ { name: 'Read', iconName: 'eye', type: ['doubleClick', 'inRow'], actionFunc: async (actionData) => { this.selectedEmail = actionData.item; if (!actionData.item.read) { this.markAsRead(actionData.item.id); } } }, { name: 'Reply', iconName: 'reply', type: ['contextmenu'], actionFunc: async (actionData) => { this.replyToEmail(actionData.item); } }, { name: 'Forward', iconName: 'share', type: ['contextmenu'], actionFunc: async (actionData) => { this.forwardEmail(actionData.item); } }, { name: 'Delete', iconName: 'trash', type: ['contextmenu'], actionFunc: async (actionData) => { this.deleteEmail(actionData.item.id); } } ]} .selectionMode=${'single'} >
`; } private renderEmailPreview() { if (!this.selectedEmail) return ''; return html`
${this.selectedEmail.subject}
From: ${this.selectedEmail.from} To: ${this.selectedEmail.to.join(', ')} Date: ${this.formatDate(this.selectedEmail.date)}
${this.selectedEmail.html ? html`
` : html`
${this.selectedEmail.body}
` }
this.selectedEmail = null}> Back this.replyToEmail(this.selectedEmail!)}> Reply this.replyAllToEmail(this.selectedEmail!)}> Reply All this.forwardEmail(this.selectedEmail!)}> Forward this.deleteEmail(this.selectedEmail!.id)} type="danger"> Delete
`; } private renderComposeModal() { return html`
{ if (e.target === e.currentTarget) this.showCompose = false; }}>
New Email
this.showCompose = false} type="ghost">
this.sendEmail(e.detail)}>
this.showCompose = false} type="secondary"> Cancel Send Email
`; } private getFilteredEmails(): IEmail[] { let emails = this.emails.filter(e => e.folder === this.selectedFolder); if (this.searchTerm) { const search = this.searchTerm.toLowerCase(); emails = emails.filter(e => e.subject.toLowerCase().includes(search) || e.from.toLowerCase().includes(search) || e.body.toLowerCase().includes(search) ); } return emails.sort((a, b) => b.date - a.date); } private getEmailCount(folder: string): number { return this.emails.filter(e => e.folder === folder && !e.read).length; } private selectFolder(folder: 'inbox' | 'sent' | 'draft' | 'trash') { this.selectedFolder = folder; this.selectedEmail = null; } private formatDate(timestamp: number): string { const date = new Date(timestamp); const now = new Date(); const diff = now.getTime() - date.getTime(); const hours = diff / (1000 * 60 * 60); if (hours < 24) { return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } else if (hours < 168) { // 7 days return date.toLocaleDateString([], { weekday: 'short' }); } else { return date.toLocaleDateString([], { month: 'short', day: 'numeric' }); } } private async loadEmails() { // TODO: Load real emails from server // For now, generate mock data this.generateMockEmails(); } private async refreshEmails() { this.isLoading = true; await this.loadEmails(); this.isLoading = false; } private async sendEmail(formData: any) { try { // TODO: Implement actual email sending console.log('Sending email:', formData); // Add to sent folder const newEmail: IEmail = { id: `email-${Date.now()}`, from: 'me@dcrouter.local', to: formData.to, cc: formData.cc || [], bcc: formData.bcc || [], subject: formData.subject, body: formData.body, date: Date.now(), read: true, folder: 'sent', }; this.emails = [...this.emails, newEmail]; this.showCompose = false; // TODO: Implement toast notification when DeesToast.show is available console.log('Email sent successfully'); } catch (error: any) { // TODO: Implement toast notification when DeesToast.show is available console.error('Failed to send email', error); } } private async markAsRead(emailId: string) { const email = this.emails.find(e => e.id === emailId); if (email) { email.read = true; this.emails = [...this.emails]; } } private async markAllAsRead() { this.emails = this.emails.map(e => e.folder === this.selectedFolder ? { ...e, read: true } : e ); } private async deleteEmail(emailId: string) { const email = this.emails.find(e => e.id === emailId); if (email) { if (email.folder === 'trash') { // Permanently delete this.emails = this.emails.filter(e => e.id !== emailId); } else { // Move to trash email.folder = 'trash'; this.emails = [...this.emails]; } if (this.selectedEmail?.id === emailId) { this.selectedEmail = null; } } } private async replyToEmail(email: IEmail) { // TODO: Open compose with reply context this.showCompose = true; } private async replyAllToEmail(email: IEmail) { // TODO: Open compose with reply all context this.showCompose = true; } private async forwardEmail(email: IEmail) { // TODO: Open compose with forward context this.showCompose = true; } private generateMockEmails() { const subjects = [ 'Server Alert: High CPU Usage', 'Daily Report - Network Activity', 'Security Update Required', 'New User Registration', 'Backup Completed Successfully', 'DNS Query Spike Detected', 'SSL Certificate Renewal Notice', 'Monthly Usage Summary', ]; const senders = [ 'monitoring@dcrouter.local', 'alerts@system.local', 'admin@company.com', 'noreply@service.com', 'support@vendor.com', ]; const bodies = [ 'This is an automated alert regarding your server status.', 'Please review the attached report for detailed information.', 'Action required: Update your security settings.', 'Your daily summary is ready for review.', 'All systems are operating normally.', ]; this.emails = Array.from({ length: 50 }, (_, i) => ({ id: `email-${i}`, from: senders[Math.floor(Math.random() * senders.length)], to: ['admin@dcrouter.local'], subject: subjects[Math.floor(Math.random() * subjects.length)], body: bodies[Math.floor(Math.random() * bodies.length)], date: Date.now() - (i * 3600000), // 1 hour apart read: Math.random() > 0.3, folder: i < 40 ? 'inbox' : i < 45 ? 'sent' : 'trash', attachments: Math.random() > 0.8 ? [{ filename: 'report.pdf', size: 1024 * 1024, contentType: 'application/pdf', }] : undefined, })); } }