+ Number(n).toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 0 }); } function parseNum(val) { if (val === '' || val === null || val === undefined) return 0; return parseFloat(String(val).replace(/[$,]/g, '')) || 0; } function phaseProgress(phase, status) { var p = String(phase).toLowerCase(); var s = String(status).toLowerCase(); if (s.indexOf('pending') > -1) return 15; if (s.indexOf('proposal') > -1) return 25; if (p.indexOf('2') > -1) return 70; if (s.indexOf('active') > -1) return 50; return 30; } function statusBadge(status) { var s = String(status).toLowerCase(); var cls = 'badge-indigo'; if (s.indexOf('active') > -1 || s.indexOf('royalty') > -1) cls = 'badge-green'; else if (s.indexOf('proposal') > -1 || s.indexOf('sent') > -1) cls = 'badge-amber'; else if (s.indexOf('pending') > -1 || s.indexOf('contract') > -1) cls = 'badge-purple'; else if (s.indexOf('hot') > -1 || s.indexOf('cohort') > -1) cls = 'badge-green'; return '' + status + ''; } function buildCharts(portfolio, pipeline) { var active = []; for (var i = 0; i < portfolio.length; i++) { if (portfolio[i][4] && parseNum(portfolio[i][4]) > 0) active.push(portfolio[i]); } var names = active.map(function(r) { return String(r[0]).split(' ').slice(0,2).join(' '); }); var revenues = active.map(function(r) { return parseNum(r[4]); }); if (revenueChart) revenueChart.destroy(); revenueChart = new Chart(document.getElementById('chart-revenue'), { type: 'bar', data: { labels: names, datasets: [{ label: 'Monthly Revenue', data: revenues, backgroundColor: revenues.map(function(_, i) { return COLORS[i % COLORS.length] + 'cc'; }), borderColor: revenues.map(function(_, i) { return COLORS[i % COLORS.length]; }), borderWidth: 1, borderRadius: 6 }] }, options: { responsive: true, plugins: { legend: { display: false } }, scales: { x: { grid: { color: 'rgba(255,255,255,.04)' }, ticks: { font: { size: 10 } } }, y: { grid: { color: 'rgba(255,255,255,.04)' }, ticks: { callback: function(v) { return '
+ (v >= 1000 ? (v/1000).toFixed(0)+'k' : v); } } } } } }); var acaciaTotal = active.reduce(function(s, r) { return s + parseNum(r[5]); }, 0); var playbookTotal = active.reduce(function(s, r) { return s + parseNum(r[6]); }, 0); if (splitChart) splitChart.destroy(); splitChart = new Chart(document.getElementById('chart-split'), { type: 'doughnut', data: { labels: ['Acacia Share', 'Playbook Share'], datasets: [{ data: [acaciaTotal, playbookTotal], backgroundColor: [PURPLE + 'cc', INDIGO + 'cc'], borderColor: [PURPLE, INDIGO], borderWidth: 1, hoverOffset: 6 }] }, options: { responsive: true, cutout: '68%', plugins: { legend: { position: 'bottom', labels: { padding: 16, usePointStyle: true, pointStyleWidth: 8 } }, tooltip: { callbacks: { label: function(ctx) { return ' ' + fmtDollar(ctx.raw); } } } } } }); var pipeStatuses = {}; for (var j = 0; j < pipeline.length; j++) { var st = String(pipeline[j][2] || 'Unknown'); pipeStatuses[st] = (pipeStatuses[st] || 0) + 1; } var pLabels = Object.keys(pipeStatuses); var pCounts = pLabels.map(function(k) { return pipeStatuses[k]; }); if (pipelineChart) pipelineChart.destroy(); pipelineChart = new Chart(document.getElementById('chart-pipeline'), { type: 'doughnut', data: { labels: pLabels, datasets: [{ data: pCounts, backgroundColor: [AMBER + 'cc', INDIGO + 'cc', GREEN + 'cc', PURPLE + 'cc'], borderColor: [AMBER, INDIGO, GREEN, PURPLE], borderWidth: 1, hoverOffset: 6 }] }, options: { responsive: true, cutout: '68%', plugins: { legend: { position: 'bottom', labels: { padding: 16, usePointStyle: true, pointStyleWidth: 8 } } } } }); } function buildPortfolio(rows) { var data = []; for (var i = 1; i < rows.length; i++) { if (rows[i][0] && String(rows[i][0]).trim() !== '') data.push(rows[i]); } document.getElementById('portfolio-count').textContent = data.length + ' churches'; var html = ''; for (var k = 0; k < data.length; k++) { var r = data[k]; var name = r[0] || '-'; var tier = r[1] || ''; var phase = r[2] || ''; var status = r[3] || ''; var share = parseNum(r[5]); var next = r[10] || ''; var channel = r[9] || ''; var pct = phaseProgress(phase, status); var sc = share > 1000 ? AMBER : GREEN; html += '
'; html += '
'; html += '
'; html += '
' + name + '
'; html += statusBadge(status); html += '' + tier + ' · ' + phase + ''; html += '
'; html += '
'; html += '
'; if (next) html += 'Next: ' + next; if (next && channel) html += ' · '; if (channel) html += channel; html += '
'; html += '
'; html += '
' + fmtDollar(share) + '
'; html += '
monthly share
'; html += '
'; } document.getElementById('portfolio-list').innerHTML = html; } function buildPipeline(rows) { var data = []; for (var i = 1; i < rows.length; i++) { if (rows[i][0] && String(rows[i][0]).trim() !== '') data.push(rows[i]); } var list = document.getElementById('pipeline-list'); if (!data.length) { list.innerHTML = '
No pipeline leads yet.
'; return; } var html = ''; for (var k = 0; k < data.length; k++) { var r = data[k]; var source = r[0] || ''; var church = r[1] || ''; var status = r[2] || ''; var prob = r[3] ? (parseFloat(r[3]) * 100).toFixed(0) + '%' : '-'; var expVal = parseNum(r[4]); var next = r[5] || ''; var isHot = String(status).toLowerCase().indexOf('hot') > -1; var ac = isHot ? AMBER : INDIGO; var bs = isHot ? 'border-color:rgba(251,191,36,.25);' : ''; html += '
'; html += '
' + (status || 'Lead') + '
'; html += '
' + (church || source) + '
'; html += '
' + source + ' · ' + prob + ' probability
'; html += '
'; html += '
' + next + '
'; html += '
' + fmtDollar(expVal) + '
'; html += '
'; } list.innerHTML = html; } function buildKPIs(summary) { function gv(i) { return summary[i] ? parseNum(summary[i][1]) : 0; } function sk(id, val) { var el = document.getElementById(id); if (el) el.innerHTML = '' + val + ''; } sk('kpi-acacia', fmtDollar(gv(0))); sk('kpi-playbook', fmtDollar(gv(1))); sk('kpi-royalty', Math.round(gv(2))); sk('kpi-pipeline', fmtDollar(gv(3))); } function exportCSV() { if (!_earningsData.length) { alert('Data still loading - try again in a moment.'); return; } var csv = _earningsData.map(function(r) { return r.map(function(c) { return '"' + String(c).replace(/"/g, '""') + '"'; }).join(','); }).join('\n'); var blob = new Blob([csv], { type: 'text/csv' }); var a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'acacia-playbook-earnings-' + new Date().toISOString().slice(0,10) + '.csv'; a.click(); } function renderData(data) { var portfolio = data.portfolio || []; var pipeline = (data.pipeline || []).slice(1).filter(function(r) { return r[0] && String(r[0]).trim() !== ''; }); _earningsData = data.earnings || []; if (portfolio.length > 1) buildPortfolio(portfolio); buildPipeline(data.pipeline || []); if (data.summary && data.summary.length) buildKPIs(data.summary); var ap = portfolio.slice(1).filter(function(r) { return r[0] && String(r[0]).trim() !== ''; }); buildCharts(ap, pipeline); var ts = data.lastUpdated ? new Date(data.lastUpdated) : new Date(); document.getElementById('last-updated').textContent = ts.toLocaleString('en-US', { month:'short', day:'numeric', hour:'numeric', minute:'2-digit' }); } var SAMPLE = { portfolio: [ ['Church','Tier','Phase','Status','Monthly Revenue','Acacia Share','Playbook Share','Earnings YTD','Drive Folder','Acquisition Channel','Next Action'], ['St. Mark Baptist','Starter','Phase 1','Proposal Sent',299,149.50,149.50,0,'','Acacia Direct','Foundation PO'], ['Montgomery COGIC','Network','Phase 2','Active Royalty',999,199.80,199.80,0,'','Conference','Q2 Report'], ['Concord Network','Playmaker','Phase 1','Cohort Active',999,499.50,499.50,0,'','Webinar','Cohort Complete'], ['Latino Church A','Starter','Phase 2','Royalty',249,49.80,49.80,0,'','Booth','Upsell Network'], ['Oasis Pilot','Accelerator','Phase 1','Contract Pending',4375,21875,21875,0,'','Oasis Network','Sign Contract'] ], earnings: [ ['Date','Church','Revenue','Phase','Acacia Share','Playbook Share','Notes'], ['3/14','St. Mark',4397,'Phase 1',2198.50,2198.50,'Proposal Sent'], ['3/1','Montgomery',2997,'Phase 1',1498.50,1498.50,'Q1 Complete'] ], pipeline: [ ['Lead Source','Church','Status','Probability','Expected,'Next Steps'], ['Booth','Booth Lead 1','Hot',0.70,2198,'Call Tomorrow'], ['Webinar','Webinar Church','Follow-Up',0.70,2198,'Send Proposal'] ], summary: [['YTD Acacia',22773.60],['Playbook',22773.60],['Royalty Churches',2],['Pipeline Value',4396]], lastUpdated: new Date().toISOString() }; function loadData() { /* Path 1: native AppScript environment */ if (typeof google !== 'undefined' && google.script && google.script.run) { google.script.run .withSuccessHandler(renderData) .withFailureHandler(function() { fetchData(); }) .getPortfolioData(); return; } /* Path 2: external embed (Lindo.ai) — fetch JSON from AppScript */ fetchData(); } function fetchData() { fetch(APPSCRIPT_URL + '?action=getData', { redirect: 'follow' }) .then(function(res) { return res.json(); }) .then(renderData) .catch(function() { renderData(SAMPLE); }); } window.addEventListener('DOMContentLoaded', loadData);