add Communications section to operations — inbox/sent brief with bounce alerts
This commit is contained in:
@@ -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
@@ -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} · ${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>
|
||||||
|
|||||||
Reference in New Issue
Block a user