333 lines
11 KiB
TypeScript
333 lines
11 KiB
TypeScript
import {
|
|
DeesElement,
|
|
customElement,
|
|
html,
|
|
css,
|
|
cssManager,
|
|
property,
|
|
state,
|
|
type TemplateResult,
|
|
} from '@design.estate/dees-element';
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
'sz-mta-list-view': SzMtaListView;
|
|
}
|
|
}
|
|
|
|
export type TEmailStatus = 'delivered' | 'bounced' | 'rejected' | 'deferred' | 'pending';
|
|
export type TEmailDirection = 'inbound' | 'outbound';
|
|
|
|
export interface IEmail {
|
|
id: string;
|
|
direction: TEmailDirection;
|
|
status: TEmailStatus;
|
|
from: string;
|
|
to: string;
|
|
subject: string;
|
|
timestamp: string;
|
|
messageId: string;
|
|
size: string;
|
|
}
|
|
|
|
@customElement('sz-mta-list-view')
|
|
export class SzMtaListView extends DeesElement {
|
|
public static demo = () => html`
|
|
<div style="padding: 24px; max-width: 1200px;">
|
|
<sz-mta-list-view
|
|
.emails=${[
|
|
{ id: '1', direction: 'outbound', status: 'delivered', from: 'noreply@serve.zone', to: 'user@example.com', subject: 'Welcome to serve.zone', timestamp: '2024-01-15 14:30:22', messageId: '<abc123@serve.zone>', size: '12.4 KB' },
|
|
{ id: '2', direction: 'outbound', status: 'bounced', from: 'alerts@serve.zone', to: 'invalid@nowhere.test', subject: 'Service Alert: CPU Usage High', timestamp: '2024-01-15 14:28:10', messageId: '<def456@serve.zone>', size: '8.2 KB' },
|
|
{ id: '3', direction: 'inbound', status: 'delivered', from: 'support@customer.com', to: 'admin@serve.zone', subject: 'Re: Infrastructure Review', timestamp: '2024-01-15 14:25:00', messageId: '<ghi789@customer.com>', size: '24.1 KB' },
|
|
{ id: '4', direction: 'outbound', status: 'rejected', from: 'billing@serve.zone', to: 'blocked@spam-domain.test', subject: 'Invoice #2024-001', timestamp: '2024-01-15 14:20:45', messageId: '<jkl012@serve.zone>', size: '45.6 KB' },
|
|
{ id: '5', direction: 'outbound', status: 'deferred', from: 'noreply@serve.zone', to: 'slow@remote-server.test', subject: 'Password Reset Request', timestamp: '2024-01-15 14:15:30', messageId: '<mno345@serve.zone>', size: '6.8 KB' },
|
|
{ id: '6', direction: 'inbound', status: 'delivered', from: 'ci@github.com', to: 'devops@serve.zone', subject: 'Build #4521 passed', timestamp: '2024-01-15 14:10:00', messageId: '<pqr678@github.com>', size: '15.3 KB' },
|
|
{ id: '7', direction: 'outbound', status: 'pending', from: 'reports@serve.zone', to: 'team@serve.zone', subject: 'Weekly Infrastructure Report', timestamp: '2024-01-15 14:05:00', messageId: '<stu901@serve.zone>', size: '102.7 KB' },
|
|
]}
|
|
></sz-mta-list-view>
|
|
</div>
|
|
`;
|
|
|
|
public static demoGroups = ['MTA'];
|
|
|
|
@property({ type: Array })
|
|
public accessor emails: IEmail[] = [];
|
|
|
|
@state()
|
|
private accessor searchQuery: string = '';
|
|
|
|
@state()
|
|
private accessor statusFilter: TEmailStatus | 'all' = 'all';
|
|
|
|
@state()
|
|
private accessor directionFilter: TEmailDirection | 'all' = 'all';
|
|
|
|
private get filteredEmails(): IEmail[] {
|
|
return this.emails.filter((email) => {
|
|
if (this.statusFilter !== 'all' && email.status !== this.statusFilter) return false;
|
|
if (this.directionFilter !== 'all' && email.direction !== this.directionFilter) return false;
|
|
if (this.searchQuery) {
|
|
const q = this.searchQuery.toLowerCase();
|
|
return (
|
|
email.from.toLowerCase().includes(q) ||
|
|
email.to.toLowerCase().includes(q) ||
|
|
email.subject.toLowerCase().includes(q) ||
|
|
email.messageId.toLowerCase().includes(q)
|
|
);
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
public static styles = [
|
|
cssManager.defaultStyles,
|
|
css`
|
|
:host {
|
|
display: block;
|
|
}
|
|
|
|
.filter-bar {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 12px;
|
|
align-items: center;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.search-input {
|
|
flex: 1;
|
|
min-width: 200px;
|
|
padding: 8px 12px;
|
|
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
|
|
border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
|
|
border-radius: 6px;
|
|
font-size: 14px;
|
|
color: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
|
outline: none;
|
|
transition: border-color 200ms ease;
|
|
}
|
|
|
|
.search-input::placeholder {
|
|
color: ${cssManager.bdTheme('#a1a1aa', '#52525b')};
|
|
}
|
|
|
|
.search-input:focus {
|
|
border-color: ${cssManager.bdTheme('#2563eb', '#3b82f6')};
|
|
}
|
|
|
|
.chip-group {
|
|
display: flex;
|
|
gap: 4px;
|
|
}
|
|
|
|
.chip {
|
|
padding: 6px 12px;
|
|
background: transparent;
|
|
border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
|
|
border-radius: 9999px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
|
cursor: pointer;
|
|
transition: all 200ms ease;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.chip:hover {
|
|
background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
|
|
color: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
|
}
|
|
|
|
.chip.active {
|
|
background: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
|
color: ${cssManager.bdTheme('#fafafa', '#18181b')};
|
|
border-color: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
|
}
|
|
|
|
.results-count {
|
|
font-size: 13px;
|
|
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.table-container {
|
|
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
|
|
border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.table-header {
|
|
display: grid;
|
|
grid-template-columns: 40px 90px 1.5fr 1.5fr 2fr 140px;
|
|
gap: 16px;
|
|
padding: 12px 16px;
|
|
background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
|
|
border-bottom: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
|
}
|
|
|
|
.table-row {
|
|
display: grid;
|
|
grid-template-columns: 40px 90px 1.5fr 1.5fr 2fr 140px;
|
|
gap: 16px;
|
|
padding: 12px 16px;
|
|
border-bottom: 1px solid ${cssManager.bdTheme('#f4f4f5', '#27272a')};
|
|
font-size: 14px;
|
|
color: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
|
align-items: center;
|
|
cursor: pointer;
|
|
transition: background 200ms ease;
|
|
}
|
|
|
|
.table-row:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.table-row:hover {
|
|
background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
|
|
}
|
|
|
|
.direction-icon {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.direction-icon.inbound {
|
|
color: ${cssManager.bdTheme('#16a34a', '#22c55e')};
|
|
}
|
|
|
|
.direction-icon.outbound {
|
|
color: ${cssManager.bdTheme('#2563eb', '#60a5fa')};
|
|
}
|
|
|
|
.status-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 2px 8px;
|
|
border-radius: 9999px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.status-badge.delivered {
|
|
background: ${cssManager.bdTheme('#dcfce7', 'rgba(34, 197, 94, 0.2)')};
|
|
color: ${cssManager.bdTheme('#16a34a', '#22c55e')};
|
|
}
|
|
|
|
.status-badge.bounced,
|
|
.status-badge.rejected {
|
|
background: ${cssManager.bdTheme('#fee2e2', 'rgba(239, 68, 68, 0.2)')};
|
|
color: ${cssManager.bdTheme('#dc2626', '#ef4444')};
|
|
}
|
|
|
|
.status-badge.deferred {
|
|
background: ${cssManager.bdTheme('#fef9c3', 'rgba(250, 204, 21, 0.2)')};
|
|
color: ${cssManager.bdTheme('#ca8a04', '#facc15')};
|
|
}
|
|
|
|
.status-badge.pending {
|
|
background: ${cssManager.bdTheme('#dbeafe', 'rgba(59, 130, 246, 0.2)')};
|
|
color: ${cssManager.bdTheme('#2563eb', '#60a5fa')};
|
|
}
|
|
|
|
.email-from,
|
|
.email-to {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.email-subject {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.email-timestamp {
|
|
font-size: 13px;
|
|
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.empty-state {
|
|
padding: 48px 24px;
|
|
text-align: center;
|
|
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
|
}
|
|
`,
|
|
];
|
|
|
|
public render(): TemplateResult {
|
|
const filtered = this.filteredEmails;
|
|
|
|
return html`
|
|
<div class="filter-bar">
|
|
<input
|
|
class="search-input"
|
|
type="text"
|
|
placeholder="Search by from, to, subject, or message ID..."
|
|
.value=${this.searchQuery}
|
|
@input=${(e: InputEvent) => { this.searchQuery = (e.target as HTMLInputElement).value; }}
|
|
/>
|
|
<div class="chip-group">
|
|
${(['all', 'inbound', 'outbound'] as const).map(dir => html`
|
|
<button
|
|
class="chip ${this.directionFilter === dir ? 'active' : ''}"
|
|
@click=${() => { this.directionFilter = dir; }}
|
|
>${dir === 'all' ? 'All' : dir === 'inbound' ? 'Inbound' : 'Outbound'}</button>
|
|
`)}
|
|
</div>
|
|
<div class="chip-group">
|
|
${(['all', 'delivered', 'bounced', 'rejected', 'deferred', 'pending'] as const).map(s => html`
|
|
<button
|
|
class="chip ${this.statusFilter === s ? 'active' : ''}"
|
|
@click=${() => { this.statusFilter = s; }}
|
|
>${s === 'all' ? 'All' : s.charAt(0).toUpperCase() + s.slice(1)}</button>
|
|
`)}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="results-count">Showing ${filtered.length} of ${this.emails.length} emails</div>
|
|
|
|
<div class="table-container">
|
|
<div class="table-header">
|
|
<span></span>
|
|
<span>Status</span>
|
|
<span>From</span>
|
|
<span>To</span>
|
|
<span>Subject</span>
|
|
<span>Timestamp</span>
|
|
</div>
|
|
${filtered.length > 0 ? filtered.map(email => html`
|
|
<div class="table-row" @click=${() => this.handleEmailClick(email)}>
|
|
<span class="direction-icon ${email.direction}">
|
|
${email.direction === 'inbound'
|
|
? html`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg>`
|
|
: html`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg>`
|
|
}
|
|
</span>
|
|
<span><span class="status-badge ${email.status}">${email.status}</span></span>
|
|
<span class="email-from" title="${email.from}">${email.from}</span>
|
|
<span class="email-to" title="${email.to}">${email.to}</span>
|
|
<span class="email-subject" title="${email.subject}">${email.subject}</span>
|
|
<span class="email-timestamp">${email.timestamp}</span>
|
|
</div>
|
|
`) : html`
|
|
<div class="empty-state">No emails found</div>
|
|
`}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private handleEmailClick(email: IEmail) {
|
|
this.dispatchEvent(new CustomEvent('email-click', { detail: email, bubbles: true, composed: true }));
|
|
}
|
|
}
|