Subversion Repositories SmartDukaan

Rev

Rev 37760 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 37760 Rev 37783
Line 88... Line 88...
88
  .fstep .fn{font-size:10.5px;text-transform:uppercase;letter-spacing:.03em;color:var(--muted);font-weight:700}
88
  .fstep .fn{font-size:10.5px;text-transform:uppercase;letter-spacing:.03em;color:var(--muted);font-weight:700}
89
  .fstep .fv{font-size:24px;font-weight:770;margin-top:6px}
89
  .fstep .fv{font-size:24px;font-weight:770;margin-top:6px}
90
  .fstep .fbar{height:5px;border-radius:3px;margin-top:9px;background:linear-gradient(90deg,var(--navy),var(--brand))}
90
  .fstep .fbar{height:5px;border-radius:3px;margin-top:9px;background:linear-gradient(90deg,var(--navy),var(--brand))}
91
  .fstep .drop{font-size:10.5px;color:var(--red);margin-top:6px;font-weight:600}
91
  .fstep .drop{font-size:10.5px;color:var(--red);margin-top:6px;font-weight:600}
92
  .fstep .keep{font-size:10.5px;color:var(--good);margin-top:6px;font-weight:600}
92
  .fstep .keep{font-size:10.5px;color:var(--good);margin-top:6px;font-weight:600}
-
 
93
  .fstep .fcap[data-drop]{cursor:pointer;text-decoration:underline dotted;text-underline-offset:2px}
-
 
94
  .fstep .fcap[data-drop]:hover{text-decoration-style:solid}
93
  .fstep .arrow{position:absolute;right:-11px;top:50%;transform:translateY(-50%);z-index:2;color:#c5cce0;font-size:14px}
95
  .fstep .arrow{position:absolute;right:-11px;top:50%;transform:translateY(-50%);z-index:2;color:#c5cce0;font-size:14px}
94
 
96
 
95
  /* table */
97
  /* table */
96
  table{width:100%;border-collapse:collapse;font-size:13px}
98
  table{width:100%;border-collapse:collapse;font-size:13px}
97
  th,td{text-align:left;padding:9px 9px;border-bottom:1px solid var(--line2)}
99
  th,td{text-align:left;padding:9px 9px;border-bottom:1px solid var(--line2)}
Line 392... Line 394...
392
    <div class="search">
394
    <div class="search">
393
      <span class="si">&#128269;</span>
395
      <span class="si">&#128269;</span>
394
      <input id="lmsSearch" placeholder="Search LMS ID, business, phone, retailer…  e.g. LMS-UPW-26-000123">
396
      <input id="lmsSearch" placeholder="Search LMS ID, business, phone, retailer…  e.g. LMS-UPW-26-000123">
395
      <span class="hint">/ to focus</span>
397
      <span class="hint">/ to focus</span>
396
    </div>
398
    </div>
-
 
399
    #if($canCreateLead)
397
    <button class="abtn coral" onclick="window.open(CTX+'/getOpenLead','_blank')">+ New Lead</button>
400
    <button class="abtn coral" onclick="window.open(CTX+'/getOpenLead','_blank')">+ New Lead</button>
-
 
401
    #end
398
    <div class="role">
402
    <div class="role">
399
      <div class="av" title="Signed in as $!viewerName">$!viewerInitials</div>
403
      <div class="av" title="Signed in as $!viewerName">$!viewerInitials</div>
400
      <div class="who"><b>$!viewerName</b><br><span>$!viewerRole</span></div>
404
      <div class="who"><b>$!viewerName</b><br><span>$!viewerRole</span></div>
401
    </div>
405
    </div>
402
  </div>
406
  </div>
Line 1176... Line 1180...
1176
  function renderFunnel(funnel){
1180
  function renderFunnel(funnel){
1177
    var byStage={}; funnel.forEach(function(s){ byStage[s.stage]=s; });
1181
    var byStage={}; funnel.forEach(function(s){ byStage[s.stage]=s; });
1178
    // First box is every lead created in the period (server key ALL) -- not the NEW stage, which is
1182
    // First box is every lead created in the period (server key ALL) -- not the NEW stage, which is
1179
    // the "New" KPI card. BEAT_PLANNED / VISITED / ONBOARDED are evidence-derived on the server.
1183
    // the "New" KPI card. BEAT_PLANNED / VISITED / ONBOARDED are evidence-derived on the server.
1180
    var seq=[['ALL','All leads'],['ASSIGNED','Assigned'],['CONTACTED','Contacted'],['QUALIFIED','Qualified'],['BEAT_PLANNED','Beat planned'],['VISITED','Visited'],['ONBOARDED','Onboarded']];
1184
    var seq=[['ALL','All leads'],['ASSIGNED','Assigned'],['CONTACTED','Contacted'],['QUALIFIED','Qualified'],['BEAT_PLANNED','Beat planned'],['VISITED','Visited'],['ONBOARDED','Onboarded']];
1181
    var h=''; var prev=null;
1185
    var h=''; var prev=null, prevKey=null;
1182
    seq.forEach(function(p,idx){ var s=byStage[p[0]]; if(!s) return; var last=(idx===seq.length-1);
1186
    seq.forEach(function(p,idx){ var s=byStage[p[0]]; if(!s) return; var last=(idx===seq.length-1);
-
 
1187
      // Leads that fell out between the previous box and this one: at the previous stage exactly,
-
 
1188
      // or -- for the first gap -- anything in scope that never reached ASSIGNED (still NEW, or
-
 
1189
      // closed as NOT_INTERESTED / DROPPED). Same buckets the drill endpoint already understands.
-
 
1190
      var dropKey = prevKey===null ? '' : (prevKey==='ALL' ? 'NEW,NOT_INTERESTED,DROPPED' : prevKey);
-
 
1191
      var dropAttr = dropKey ? ' data-drop="'+esc(dropKey)+'" data-droplabel="'+esc('Dropped before '+p[1])+'" title="Open the leads that did not reach '+esc(p[1])+'"' : '';
1183
      var cap; if(idx===0){ cap='<div class="keep">created</div>'; }
1192
      var cap; if(idx===0){ cap='<div class="keep">created</div>'; }
1184
      else if(last){ cap='<div class="keep">'+s.pct+'% conversion</div>'; }
1193
      else if(last){ var dropL=prev?prev.count-s.count:0;
-
 
1194
        cap='<div class="keep"><span class="fcap"'+dropAttr+'>'+s.pct+'% conversion'+(dropL>0?(' · &minus;'+fmtInt(dropL)):'')+'</span></div>'; }
-
 
1195
      else { var drop=prev?prev.count-s.count:0;
1185
      else { var drop=prev?prev.count-s.count:0; cap=drop>0?('<div class="drop">&minus;'+fmtInt(drop)+'</div>'):('<div class="keep">'+s.pct+'%</div>'); }
1196
        cap=drop>0?('<div class="drop"><span class="fcap"'+dropAttr+'>&minus;'+fmtInt(drop)+'</span></div>')
-
 
1197
                  :('<div class="keep"><span class="fcap"'+dropAttr+'>'+s.pct+'%</span></div>'); }
1186
      h+='<div class="fstep" data-stage="'+p[0]+'" data-label="'+esc(p[1])+'"><div class="fn">'+esc(p[1])+'</div><div class="fv">'+fmtInt(s.count)+'</div>'+
1198
      h+='<div class="fstep" data-stage="'+p[0]+'" data-label="'+esc(p[1])+'"><div class="fn">'+esc(p[1])+'</div><div class="fv">'+fmtInt(s.count)+'</div>'+
1187
        '<div class="fbar" style="width:'+Math.max(s.pct,3)+'%"></div>'+cap+(last?'':'<span class="arrow">&#10148;</span>')+'</div>';
1199
        '<div class="fbar" style="width:'+Math.max(s.pct,3)+'%"></div>'+cap+(last?'':'<span class="arrow">&#10148;</span>')+'</div>';
1188
      prev=s; });
1200
      prev=s; prevKey=p[0]; });
1189
    byId('ccFunnel').innerHTML=h||'<div class="ph">No leads in scope.</div>';
1201
    byId('ccFunnel').innerHTML=h||'<div class="ph">No leads in scope.</div>';
1190
  }
1202
  }
1191
 
1203
 
1192
  // ---- Lead pipeline: filtered, sorted and paged on the server ------------------------------
1204
  // ---- Lead pipeline: filtered, sorted and paged on the server ------------------------------
1193
  // PIPE.q is the search term. While it is set the date filter does not apply — a search hunts for
1205
  // PIPE.q is the search term. While it is set the date filter does not apply — a search hunts for
Line 1512... Line 1524...
1512
    var la=e.target.closest('tr[data-liveagent]');
1524
    var la=e.target.closest('tr[data-liveagent]');
1513
    if(la){ openAgentCalls(parseInt(la.getAttribute('data-liveagent'),10)); return; }
1525
    if(la){ openAgentCalls(parseInt(la.getAttribute('data-liveagent'),10)); return; }
1514
    var th=e.target.closest('th.sortable'); if(th){ pipeSort(th.getAttribute('data-sort')); return; }
1526
    var th=e.target.closest('th.sortable'); if(th){ pipeSort(th.getAttribute('data-sort')); return; }
1515
    var kpi=e.target.closest('.kpi[data-kpi]'); if(kpi){ var b=kpiBucket(kpi.getAttribute('data-kpi'));
1527
    var kpi=e.target.closest('.kpi[data-kpi]'); if(kpi){ var b=kpiBucket(kpi.getAttribute('data-kpi'));
1516
      drill('Command Center',kpi.querySelector('.label').textContent,'KPI drill',b[0],b[1]); return; }
1528
      drill('Command Center',kpi.querySelector('.label').textContent,'KPI drill',b[0],b[1]); return; }
-
 
1529
    var fc=e.target.closest('.fcap[data-drop]'); if(fc){ var dk=fc.getAttribute('data-drop');
-
 
1530
      drill('Lifecycle funnel',fc.getAttribute('data-droplabel'),'At the previous step, never reached this one',
-
 
1531
            dk.indexOf(',')>=0?'STAGE_IN':'STAGE',dk); return; }
1517
    var fs=e.target.closest('.fstep[data-stage]'); if(fs){ var st=fs.getAttribute('data-stage');
1532
    var fs=e.target.closest('.fstep[data-stage]'); if(fs){ var st=fs.getAttribute('data-stage');
1518
      drill('Lifecycle funnel',fs.getAttribute('data-label'),'Reached this stage',st==='ALL'?'ALL':'STAGE_REACHED',st==='ALL'?'':st); return; }
1533
      drill('Lifecycle funnel',fs.getAttribute('data-label'),'Reached this stage',st==='ALL'?'ALL':'STAGE_REACHED',st==='ALL'?'':st); return; }
1519
    var att=e.target.closest('.att[data-att]'); if(att){ drill('Needs attention',att.getAttribute('data-attt'),'Attention queue','ATTENTION',att.getAttribute('data-att')); return; }
1534
    var att=e.target.closest('.att[data-att]'); if(att){ drill('Needs attention',att.getAttribute('data-attt'),'Attention queue','ATTENTION',att.getAttribute('data-att')); return; }
1520
    var reg=e.target.closest('tr[data-region]'); if(reg){ drill('State funnel',reg.getAttribute('data-rn'),'Region '+reg.getAttribute('data-region'),'REGION',reg.getAttribute('data-region')); return; }
1535
    var reg=e.target.closest('tr[data-region]'); if(reg){ drill('State funnel',reg.getAttribute('data-rn'),'Region '+reg.getAttribute('data-region'),'REGION',reg.getAttribute('data-region')); return; }
1521
    var rep=e.target.closest('tr[data-rep]'); if(rep){ openReport(rep.getAttribute('data-rep')); return; }
1536
    var rep=e.target.closest('tr[data-rep]'); if(rep){ openReport(rep.getAttribute('data-rep')); return; }