widget-template.html
Language HTML7.1 KB99 lines
Scaffold for the pipeline board widget passed to visualize:show_widget.Replace the D array with the parsed leads. The script computes theper-owner stacked bar and builds the filterable, linked table.Each lead object: {id, n: "title", o: "Owner Full Name", s: "Stage Name", g: "intake|active|parked"}Metric card numbers (Total / Active / Parked / Intake) and the owner filterpill counts are hardcoded in the markup below. Recompute them from D beforeemitting, or generate the cards/pills from D in the script. Owner pills mustlist every owner present in D, highest count first.----------------------------------------------------------------------<div style="padding: 1rem 0;"><h2 class="sr-only">Interactive board of open Meridian Display Co. leads grouped by owner and pipeline stage, each linking to its Bitrix record.</h2><div style="display:grid; grid-template-columns:repeat(auto-fit,minmax(120px,1fr)); gap:12px; margin-bottom:1.25rem;"><div style="background:var(--color-background-secondary); border-radius:var(--border-radius-md); padding:1rem;"><div style="font-size:13px; color:var(--color-text-secondary);">Total open</div><div style="font-size:24px; font-weight:500;" id="m-total">0</div></div><div style="background:var(--color-background-secondary); border-radius:var(--border-radius-md); padding:1rem;"><div style="font-size:13px; color:var(--color-text-secondary);">Active</div><div style="font-size:24px; font-weight:500;" id="m-active">0</div></div><div style="background:var(--color-background-secondary); border-radius:var(--border-radius-md); padding:1rem;"><div style="font-size:13px; color:var(--color-text-secondary);">Parked</div><div style="font-size:24px; font-weight:500;" id="m-parked">0</div></div><div style="background:var(--color-background-secondary); border-radius:var(--border-radius-md); padding:1rem;"><div style="font-size:13px; color:var(--color-text-secondary);">Intake / new</div><div style="font-size:24px; font-weight:500;" id="m-intake">0</div></div></div><div style="display:flex; flex-wrap:wrap; gap:16px; margin-bottom:8px; font-size:12px; color:var(--color-text-secondary);"><span style="display:flex; align-items:center; gap:4px;"><span style="width:10px;height:10px;border-radius:2px;background:#888780;"></span>Intake</span><span style="display:flex; align-items:center; gap:4px;"><span style="width:10px;height:10px;border-radius:2px;background:#1D9E75;"></span>Active</span><span style="display:flex; align-items:center; gap:4px;"><span style="width:10px;height:10px;border-radius:2px;background:#BA7517;"></span>Parked</span></div><div style="position:relative; width:100%; height:260px; margin-bottom:1.5rem;"><canvas id="ownerChart" role="img" aria-label="Stacked bar chart of open leads by owner, split into intake, active and parked.">Open leads by owner.</canvas></div><div id="pills" style="display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-bottom:12px;"><select id="stageSel" style="margin-left:auto; width:auto; min-width:160px;"></select></div><div id="tableWrap" style="border:0.5px solid var(--color-border-tertiary); border-radius:var(--border-radius-md); overflow:hidden;"></div><script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js"></script><script>const D=[ /* parsed leads go here */ ];const BASE="https://meridian.bitrix24.example/crm/lead/details/";const gColor={intake:"#888780",active:"#1D9E75",parked:"#BA7517"};document.getElementById('m-total').textContent=D.length;['intake','active','parked'].forEach(g=>{const e=document.getElementById('m-'+g);if(e)e.textContent=D.filter(d=>d.g===g).length;});const owners=[...new Set(D.map(d=>d.o))].sort((a,b)=>D.filter(d=>d.o===b).length-D.filter(d=>d.o===a).length);const stack={intake:[],active:[],parked:[]};owners.forEach(o=>{["intake","active","parked"].forEach(g=>{stack[g].push(D.filter(d=>d.o===o&&d.g===g).length);});});new Chart(document.getElementById('ownerChart'),{type:'bar',data:{labels:owners.map(o=>o.split(' ')[0]),datasets:[{label:'Intake',data:stack.intake,backgroundColor:gColor.intake},{label:'Active',data:stack.active,backgroundColor:gColor.active},{label:'Parked',data:stack.parked,backgroundColor:gColor.parked}]},options:{indexAxis:'y',responsive:true,maintainAspectRatio:false,scales:{x:{stacked:true,ticks:{precision:0}},y:{stacked:true}},plugins:{legend:{display:false}}}});const pills=document.getElementById('pills');const stageSel=document.getElementById('stageSel');const mkPill=(label,o)=>{const s=document.createElement('span');s.className='owner-f';s.dataset.o=o;s.textContent=label;s.style.cssText='cursor:pointer;font-size:13px;padding:5px 12px;border:0.5px solid var(--color-border-secondary);border-radius:var(--border-radius-md);';return s;};const allPill=mkPill('All ('+D.length+')','all');allPill.style.background='var(--color-background-secondary)';pills.insertBefore(allPill,stageSel);owners.forEach(o=>{const c=D.filter(d=>d.o===o).length;pills.insertBefore(mkPill(o.split(' ')[0]+' ('+c+')',o),stageSel);});const stages=[...new Set(D.map(d=>d.s))];stageSel.innerHTML='<option value="all">All stages</option>'+stages.map(s=>`<option value="${s}">${s} (${D.filter(d=>d.s===s).length})</option>`).join('');let curO="all",curS="all";function badge(g){return `<span style="font-size:11px;padding:2px 8px;border-radius:var(--border-radius-md);background:${g==='parked'?'#FAEEDA':g==='intake'?'#F1EFE8':'#E1F5EE'};color:${g==='parked'?'#633806':g==='intake'?'#444441':'#0F6E56'};">`;}function render(){const rows=D.filter(d=>(curO==="all"||d.o===curO)&&(curS==="all"||d.s===curS));let h='<table style="width:100%;border-collapse:collapse;font-size:13px;table-layout:fixed;"><thead><tr style="background:var(--color-background-secondary);text-align:left;"><th style="padding:8px 10px;width:62px;">ID</th><th style="padding:8px 10px;">Lead</th><th style="padding:8px 10px;width:150px;">Stage</th><th style="padding:8px 10px;width:90px;">Owner</th></tr></thead><tbody>';rows.forEach(d=>{h+=`<tr style="border-top:0.5px solid var(--color-border-tertiary);"><td style="padding:7px 10px;"><a href="${BASE}${d.id}/" style="color:var(--color-text-info);text-decoration:none;">${d.id}<i class="ti ti-external-link" style="font-size:13px;vertical-align:-1px;margin-left:3px;" aria-hidden="true"></i></a></td><td style="padding:7px 10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${d.n}</td><td style="padding:7px 10px;">${badge(d.g)}${d.s}</span></td><td style="padding:7px 10px;color:var(--color-text-secondary);">${d.o.split(' ')[0]}</td></tr>`;});h+='</tbody></table>';if(!rows.length)h='<div style="padding:1rem;color:var(--color-text-secondary);">No leads match.</div>';document.getElementById('tableWrap').innerHTML=h;}document.querySelectorAll('.owner-f').forEach(el=>el.addEventListener('click',()=>{curO=el.dataset.o;document.querySelectorAll('.owner-f').forEach(x=>x.style.background='');el.style.background='var(--color-background-secondary)';render();}));stageSel.addEventListener('change',()=>{curS=stageSel.value;render();});render();</script></div>