diff --git a/index.html b/index.html index f9ab8ae..0f7329f 100644 --- a/index.html +++ b/index.html @@ -12,7 +12,7 @@ .filters{display:grid;grid-template-columns:repeat(8,minmax(125px,1fr));gap:12px;padding:16px;margin-bottom:16px}.field label{display:block;color:var(--muted);font-size:12px;margin-bottom:5px} select,input{width:100%;height:36px;border:1px solid #cfd7df;border-radius:7px;background:#fff;padding:0 9px;color:var(--text)} select[multiple]{height:72px;padding:4px 8px}.range-note{font-size:11px;color:var(--muted);margin-top:4px} - .cards{display:grid;grid-template-columns:repeat(9,1fr);gap:12px;margin-bottom:16px}.card{padding:16px}.card .label{color:var(--muted);font-size:12px}.card .value{font-size:23px;font-weight:650;margin-top:5px;font-variant-numeric:tabular-nums} + .cards{display:grid;grid-template-columns:repeat(9,1fr);gap:12px;margin-bottom:16px}.card{padding:16px;min-width:0}.card .label{color:var(--muted);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.card .value{font-size:23px;font-weight:650;margin-top:5px;font-variant-numeric:tabular-nums} .grid{margin-bottom:16px}.chart{padding:18px;min-height:340px;position:relative}.title{font-size:16px;font-weight:650;margin-bottom:12px}.chart-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}.chart-sub{font-size:12px;color:var(--muted);margin-top:-8px} svg{width:100%;height:270px;margin-top:8px}.axis{stroke:#d8dfe6}.gridline{stroke:#edf1f4}.spend-line{fill:none;stroke:var(--brand);stroke-width:2.5}.conv-line{fill:none;stroke:var(--good);stroke-width:2.5}.legend{display:flex;gap:8px}.legend button{border:1px solid var(--line);border-radius:16px;background:#fff;color:var(--muted);padding:5px 10px;cursor:pointer}.legend button.off{opacity:.4}.dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:5px}.axis-label{fill:#74808c;font-size:11px}.axis-title{font-size:11px;font-weight:650}.point{stroke:#fff;stroke-width:2;cursor:crosshair}.hover-line{stroke:#9aa7b3;stroke-dasharray:3 3}.tooltip{position:absolute;display:none;pointer-events:none;background:#17212b;color:#fff;border-radius:7px;padding:8px 10px;font-size:12px;line-height:1.55;box-shadow:0 4px 14px #0003;z-index:2}.num{text-align:right;font-variant-numeric:tabular-nums} .table-panel{padding:18px}.table-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}.table-note{color:var(--muted);font-size:12px}.table-wrap{overflow:auto;max-height:640px;border:1px solid var(--line);border-radius:8px} @@ -52,8 +52,9 @@ function updateDateBounds(reset=false){const months=selected('month'),rows=ancho function updateAnchoredFilters(resetDates=false){const rows=anchorRows();multiOptions('month',rows.map(r=>r.date.slice(0,7)));singleOptions('geo',rows.map(r=>r.geo));singleOptions('source',rows.map(r=>r.source));multiOptions('group',rows.map(r=>r.group));updateDateBounds(resetDates)} function filtered(){const q=$('search').value.trim().toLowerCase(),months=selected('month'),groups=selected('group');return all.filter(r=>r.status==='Active'&&(!$('pkg').value||r.pkg===$('pkg').value)&&(!months.length||months.includes(r.date.slice(0,7)))&&(!$('geo').value||r.geo===$('geo').value)&&(!$('source').value||r.source===$('source').value)&&(!groups.length||groups.includes(r.group))&&(!$('start').value||r.date>=$('start').value)&&(!$('end').value||r.date<=$('end').value)&&(!q||(`${r.campaign} ${r.adpingId}`).toLowerCase().includes(q)))} const tracksEvent=r=>{const k=String(r.kpi||'').trim();return k&&k!=='-'}; +function eventLabel(rows){const names=[...new Set(rows.filter(tracksEvent).map(r=>String(r.kpi).trim()))];return names.length?names.join(' / '):'KPI Event'} function aggregate(rows,keys){const m=new Map;for(const r of rows){const k=keys.map(x=>r[x]).join('\u0001');if(!m.has(k))m.set(k,{...Object.fromEntries(keys.map(x=>[x,r[x]])),impression:0,click:0,conversions:0,spend:0,event:0,eventSpend:0,eventKnown:false,kpi:r.kpi||''});const o=m.get(k);for(const x of ['impression','click','conversions','spend'])o[x]+=+r[x]||0;if(r.event!==null&&r.event!==undefined&&r.event!==''){o.event+=+r.event||0;o.eventSpend+=+r.spend||0;o.eventKnown=true}}return [...m.values()]} -function cards(rows){const t=aggregate(rows,[])[0]||{impression:0,click:0,conversions:0,spend:0},e=aggregate(rows.filter(tracksEvent),[])[0]||{event:0,eventKnown:false,eventSpend:0};const vals=[['Spend',money(t.spend)],['Event',e.eventKnown?n(e.event):'—'],['CAC',e.eventKnown&&e.event?money(e.eventSpend/e.event):'—'],['Conversions',n(t.conversions)],['CPI',t.conversions?money(t.spend/t.conversions):'—'],['Impression',n(t.impression)],['Click',n(t.click)],['CTR',pct(rate(t.click,t.impression))],['CVR',pct(rate(t.conversions,t.click))]];$('cards').innerHTML=vals.map(x=>`