add Communications section to operations — inbox/sent brief with bounce alerts

This commit is contained in:
Oliver
2026-06-09 18:52:29 -03:00
parent d8d487e5d2
commit d1fac009c3
2 changed files with 165 additions and 0 deletions
+5
View File
@@ -36,6 +36,11 @@
} }
], ],
"state_of_play": "Prelaunch phase. CRM has 89 contacts (60 YouTube creators on Hold, 1 homepage chat lead). 0 subscriptions, 0 influencers onboarded, 0 cold emails sent. The site is live at derez.ai with pricing, coupon badges (friends950, creator950, friends499, friends1750), and Plausible analytics. Next milestone: first paying customer.", "state_of_play": "Prelaunch phase. CRM has 89 contacts (60 YouTube creators on Hold, 1 homepage chat lead). 0 subscriptions, 0 influencers onboarded, 0 cold emails sent. The site is live at derez.ai with pricing, coupon badges (friends950, creator950, friends499, friends1750), and Plausible analytics. Next milestone: first paying customer.",
"comms": {
"inbox": { "total": 4, "last35h": 2 },
"sent": { "total": 8, "last35h": 2 },
"alerts": 1
},
"recommendations": [ "recommendations": [
"Prioritise the top 20 YouTube creators from the 60 collected — these are your most winnable influencer channel.", "Prioritise the top 20 YouTube creators from the 60 collected — these are your most winnable influencer channel.",
"Unlock cold email outreach by populating real business emails for those 20 creators. The templates and coupons are ready.", "Unlock cold email outreach by populating real business emails for those 20 creators. The templates and coupons are ready.",
+160
View File
@@ -368,8 +368,95 @@
font-weight: 700; font-weight: 700;
} }
/* ── Communications ──────────────────────── */
.comms-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
}
.comms-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-card);
padding: 20px;
}
.comms-card h3 {
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--accent);
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 8px;
}
.comms-card .comms-summary {
display: flex;
gap: 20px;
margin-bottom: 12px;
}
.comms-card .comms-stat {
text-align: center;
}
.comms-card .comms-stat-num {
font-size: 24px;
font-weight: 700;
color: var(--text);
}
.comms-card .comms-stat-label {
font-size: 10px;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.04em;
}
.comms-list { list-style: none; }
.comms-list li {
font-size: 12px;
padding: 6px 0;
border-bottom: 1px solid rgba(10,48,96,0.3);
display: flex;
align-items: flex-start;
gap: 8px;
line-height: 1.5;
}
.comms-list li:last-child { border-bottom: none; }
.comms-list .comms-time {
color: var(--text-muted);
font-size: 11px;
white-space: nowrap;
flex-shrink: 0;
min-width: 60px;
}
.comms-list .comms-subj {
color: var(--text);
}
.comms-list .comms-detail {
color: var(--text-muted);
font-size: 11px;
}
.comms-list .comms-from {
color: var(--accent);
font-size: 11px;
}
.comms-alert {
background: var(--bg-card);
border: 1px solid rgba(255,59,59,0.3);
border-left: 3px solid var(--danger);
border-radius: var(--radius-card);
padding: 12px 16px;
margin-top: 10px;
font-size: 12px;
display: flex;
align-items: flex-start;
gap: 8px;
}
.comms-alert .alert-icon { color: var(--danger); flex-shrink: 0; font-size: 14px; }
.comms-alert .alert-body { color: var(--text); }
.comms-alert .alert-detail { color: var(--text-muted); font-size: 11px; margin-top: 2px; }
@media (max-width: 900px) { @media (max-width: 900px) {
.campaign-grid { grid-template-columns: 1fr; } .campaign-grid { grid-template-columns: 1fr; }
.comms-grid { grid-template-columns: 1fr; }
.goals-grid { grid-template-columns: repeat(2, 1fr); } .goals-grid { grid-template-columns: repeat(2, 1fr); }
.strategy-grid { grid-template-columns: 1fr; } .strategy-grid { grid-template-columns: 1fr; }
} }
@@ -427,6 +514,17 @@
</div> </div>
</div> </div>
<!-- ═══════════════ COMMUNICATIONS ═══════════════ -->
<div class="section">
<div class="section-header" onclick="toggleSection('comms-section', this)">
<h2><span class="icon"></span> Communications <span class="count">last 35 hours</span></h2>
<span class="section-toggle"></span>
</div>
<div class="section-body" id="comms-section">
<div class="comms-grid" id="comms-grid"></div>
</div>
</div>
<!-- ═══════════════ STRATEGY & RECOMMENDATIONS ═══════════════ --> <!-- ═══════════════ STRATEGY & RECOMMENDATIONS ═══════════════ -->
<div class="section"> <div class="section">
<div class="section-header" onclick="toggleSection('strategy-section', this)"> <div class="section-header" onclick="toggleSection('strategy-section', this)">
@@ -512,6 +610,30 @@
} }
]; ];
const comms = {
inbox: {
total: 4,
last35h: 2,
items: [
{ time: 'Jun 09 14:44', from: 'Mail Delivery System', subj: 'Undelivered — the@123.de', detail: '421 Downstream server error — cold email bounced', alert: true },
{ time: 'Jun 08 21:04', from: 'Mail Delivery System', subj: 'Undelivered — the@123.de', detail: '421 Downstream server error', alert: false },
],
alerts: [
{ icon: '⚠', body: 'Cold email to the@123.de bouncing (421 Downstream server error)', detail: 'Check recipient email validity or retry later.' }
]
},
sent: {
total: 8,
last35h: 2,
items: [
{ time: 'Jun 09', to: 'o.arnold@projektbox.de', subj: 'setting up an agent for projektbox' },
{ time: 'Jun 08', to: 'oliver@odoo4projects.com', subj: 'managing your agents without SSH' },
],
top_sent: 'Mark — Customer Success Agent'
},
note: '2 bounce alerts need review. Sending from support@derez.ai as Mark.'
};
const recommendations = [ const recommendations = [
'Prioritise the top 20 YouTube creators from the 60 collected — these are your most winnable influencer channel.', 'Prioritise the top 20 YouTube creators from the 60 collected — these are your most winnable influencer channel.',
'Unlock cold email outreach by populating real business emails for those 20 creators. Templates and coupons are ready.', 'Unlock cold email outreach by populating real business emails for those 20 creators. Templates and coupons are ready.',
@@ -586,6 +708,43 @@
).join(''); ).join('');
} }
function renderCommunications() {
const c = comms;
const inboxItems = c.inbox.items.map(i =>
`<li><span class="comms-time">${i.time}</span><span><span class="comms-from">${i.from}</span><br><span class="comms-subj">${i.subj}</span> <span class="comms-detail">${i.detail}</span></span></li>`
).join('');
const alerts = c.inbox.alerts.map(a =>
`<div class="comms-alert"><span class="alert-icon">${a.icon}</span><div><div class="alert-body">${a.body}</div><div class="alert-detail">${a.detail}</div></div></div>`
).join('');
const sentItems = c.sent.items.map(i =>
`<li><span class="comms-time">${i.time}</span><span><span class="comms-from">${i.to}</span><br><span class="comms-subj">${i.subj}</span></span></li>`
).join('');
document.getElementById('comms-grid').innerHTML =
`<div class="comms-card">
<h3>📥 Inbox</h3>
<div class="comms-summary">
<div class="comms-stat"><div class="comms-stat-num">${c.inbox.total}</div><div class="comms-stat-label">Total</div></div>
<div class="comms-stat"><div class="comms-stat-num">${c.inbox.last35h}</div><div class="comms-stat-label">Last 35h</div></div>
<div class="comms-stat"><div class="comms-stat-num">${c.inbox.alerts.length}</div><div class="comms-stat-label">Alerts</div></div>
</div>
<ul class="comms-list">${inboxItems}</ul>
${alerts}
</div>
<div class="comms-card">
<h3>📤 Sent</h3>
<div class="comms-summary">
<div class="comms-stat"><div class="comms-stat-num">${c.sent.total}</div><div class="comms-stat-label">Total Sent</div></div>
<div class="comms-stat"><div class="comms-stat-num">${c.sent.last35h}</div><div class="comms-stat-label">Last 35h</div></div>
<div class="comms-stat"><div class="comms-stat-num">${c.sent.items.length}</div><div class="comms-stat-label">Recent</div></div>
</div>
<ul class="comms-list">${sentItems}</ul>
<div style="margin-top:8px;font-size:11px;color:var(--text-muted);">
Sender: ${c.sent.top_sent} &middot; ${c.note}
</div>
</div>`;
}
async function fetchLiveData() { async function fetchLiveData() {
try { try {
const res = await fetch('data/operations.json?t=' + Date.now()); const res = await fetch('data/operations.json?t=' + Date.now());
@@ -602,6 +761,7 @@
renderGoals(); renderGoals();
renderCampaigns(); renderCampaigns();
renderActions(); renderActions();
renderCommunications();
renderStrategy(); renderStrategy();
fetchLiveData(); fetchLiveData();
</script> </script>