Subversion Repositories SmartDukaan

Rev

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

Rev 36962 Rev 37020
Line 439... Line 439...
439
  h+='<div class="sectionlabel">Today\'s Journey Funnel</div><div class="card"><div class="funnel">';
439
  h+='<div class="sectionlabel">Today\'s Journey Funnel</div><div class="card"><div class="funnel">';
440
  var sp=f.scheduledPjp||1, pin=f.punchedIn||1;
440
  var sp=f.scheduledPjp||1, pin=f.punchedIn||1;
441
  h+=fstep('Punched In',f.punchedIn,pct(f.punchedIn,sp),f.punchedIn+' of '+f.scheduledPjp+' scheduled',false,false,'punchedIn');
441
  h+=fstep('Punched In',f.punchedIn,pct(f.punchedIn,sp),f.punchedIn+' of '+f.scheduledPjp+' scheduled',false,false,'punchedIn');
442
  h+=fstep('Traveling',f.travelling,pct(f.travelling,pin),'on the road',false,false,'travelling');
442
  h+=fstep('Traveling',f.travelling,pct(f.travelling,pin),'on the road',false,false,'travelling');
443
  h+=fstep('In Store',f.inStore,pct(f.inStore,pin),'at a stop',false,false,'inStore');
443
  h+=fstep('In Store',f.inStore,pct(f.inStore,pin),'at a stop',false,false,'inStore');
444
  h+=fstep('Idle / Break',f.idle,pct(f.idle,pin),'off-store &gt; 40 min',true,false,'idle');
444
  h+=fstep('Idle / Break',f.idle,pct(f.idle,pin),'&lt; 1 km in 15 min',true,false,'idle');
445
  h+=fstep('Punched Out',f.punchedOut,pct(f.punchedOut,pin),'journey ended',false,true,'punchedOut');
445
  h+=fstep('Punched Out',f.punchedOut,pct(f.punchedOut,pin),'journey ended',false,true,'punchedOut');
446
  h+='</div></div>';
446
  h+='</div></div>';
447
 
447
 
448
  window._boardFilter = null;             // funnel-card filter (null = all rows)
448
  window._boardFilter = null;             // funnel-card filter (null = all rows)
449
  h+='<div class="card"><h2>Live executive board</h2><p class="desc">Each executive\'s journey right now. Click a row for the day\'s visit detail.</p><div id="bj-board">'+boardCard()+'</div></div>';
449
  h+='<div class="card"><h2>Live executive board</h2><p class="desc">Each executive\'s journey right now. Click a row for the day\'s visit detail.</p><div id="bj-board">'+boardCard()+'</div></div>';
Line 494... Line 494...
494
function boardCounts(r){
494
function boardCounts(r){
495
  var completed=r.completedVisits||0, deferred=r.deferred||0;
495
  var completed=r.completedVisits||0, deferred=r.deferred||0;
496
  var total=Math.max(r.planned||0, completed+deferred);
496
  var total=Math.max(r.planned||0, completed+deferred);
497
  return {total:total, completed:completed, deferred:deferred, pending:total-completed-deferred};
497
  return {total:total, completed:completed, deferred:deferred, pending:total-completed-deferred};
498
}
498
}
-
 
499
// seconds -> compact "1h 20m" / "45m" for the board's time columns.
-
 
500
function bhm(sec){ sec=Math.max(0,Math.round(sec||0)); var h=Math.floor(sec/3600), m=Math.round((sec%3600)/60); return h?(h+'h '+m+'m'):(m+'m'); }
499
function boardTable(rows, date){
501
function boardTable(rows, date){
500
  if(!rows||!rows.length) return '<p class="desc">No executives scheduled or active for this date.</p>';
502
  if(!rows||!rows.length) return '<p class="desc">No executives scheduled or active for this date.</p>';
501
  var h='<div style="overflow-x:auto"><table><thead><tr>'+
503
  var h='<div style="overflow-x:auto"><table><thead><tr>'+
502
    '<th>Agenda</th><th>Executive</th><th>Punch In/Out</th>'+
504
    '<th>Agenda</th><th>Executive</th><th>Punch In/Out</th>'+
503
    '<th class="num" title="Planned partner visits + self-assigned leads">Total</th>'+
505
    '<th class="num" title="Planned partner visits + self-assigned leads">Total</th>'+
504
    '<th class="num">Completed</th><th class="num">Pending</th><th class="num">Deferred</th>'+
506
    '<th class="num">Completed</th><th class="num">Pending</th><th class="num">Deferred</th>'+
-
 
507
    '<th class="num" title="Transit time between stops so far today">Transit</th>'+
-
 
508
    '<th class="num" title="Time spent inside stores so far today">In-store</th>'+
505
    '<th>State</th><th>Action</th>'+
509
    '<th>State</th><th>Action</th>'+
506
    '</tr></thead><tbody>';
510
    '</tr></thead><tbody>';
507
  // column totals, computed up front so the Total row can lead the table
511
  // column totals, computed up front so the Total row can lead the table
508
  var t={total:0,completed:0,pending:0,deferred:0};
512
  var t={total:0,completed:0,pending:0,deferred:0,transit:0,inStore:0};
509
  rows.forEach(function(r){
513
  rows.forEach(function(r){
510
    var c=boardCounts(r);
514
    var c=boardCounts(r);
511
    t.total+=c.total; t.completed+=c.completed; t.pending+=c.pending; t.deferred+=c.deferred;
515
    t.total+=c.total; t.completed+=c.completed; t.pending+=c.pending; t.deferred+=c.deferred;
-
 
516
    t.transit+=(r.transitSecs||0); t.inStore+=(r.inStoreSecs||0);
512
  });
517
  });
513
  h+='<tr class="boardtot">'+
518
  h+='<tr class="boardtot">'+
514
     '<td></td><td><b>Total</b><br><span style="color:#97a1b5;font-size:11px">'+rows.length+' executives</span></td><td></td>'+
519
     '<td></td><td><b>Total</b><br><span style="color:#97a1b5;font-size:11px">'+rows.length+' executives</span></td><td></td>'+
515
     '<td class="num cnt cmuted">'+t.total+'</td>'+
520
     '<td class="num cnt cmuted">'+t.total+'</td>'+
516
     '<td class="num cnt cgreen">'+t.completed+'</td>'+
521
     '<td class="num cnt cgreen">'+t.completed+'</td>'+
517
     '<td class="num cnt">'+t.pending+'</td>'+
522
     '<td class="num cnt">'+t.pending+'</td>'+
-
 
523
     '<td class="num cnt camber">'+t.deferred+'</td>'+
-
 
524
     '<td class="num cnt cmuted">'+bhm(t.transit)+'</td>'+
518
     '<td class="num cnt camber">'+t.deferred+'</td><td></td><td></td>'+
525
     '<td class="num cnt cmuted">'+bhm(t.inStore)+'</td><td></td><td></td>'+
519
     '</tr>';
526
     '</tr>';
520
  rows.forEach(function(r){
527
  rows.forEach(function(r){
521
    var rowcls = r.state==='NOT_STARTED' ? 'rowbad' : (r.idleAway?'rowwarn':'');
528
    var rowcls = r.state==='NOT_STARTED' ? 'rowbad' : (r.idleAway?'rowwarn':'');
522
    var A = r.userId+",'"+js(r.name)+"','"+date+"'";   // openExec args
529
    var A = r.userId+",'"+js(r.name)+"','"+date+"'";   // openExec args
523
    var go = 'onclick="openExec('+A+')"';
530
    var go = 'onclick="openExec('+A+')"';
Line 529... Line 536...
529
         ' <span style="color:#97a1b5;font-size:11px">/ '+(r.punchOutTime?esc(r.punchOutTime):'--')+'</span></td>'+
536
         ' <span style="color:#97a1b5;font-size:11px">/ '+(r.punchOutTime?esc(r.punchOutTime):'--')+'</span></td>'+
530
       '<td class="num cnt cmuted">'+c.total+'</td>'+
537
       '<td class="num cnt cmuted">'+c.total+'</td>'+
531
       '<td class="num cnt cgreen">'+c.completed+'</td>'+
538
       '<td class="num cnt cgreen">'+c.completed+'</td>'+
532
       '<td class="num cnt">'+c.pending+'</td>'+
539
       '<td class="num cnt">'+c.pending+'</td>'+
533
       '<td class="num cnt camber">'+c.deferred+'</td>'+
540
       '<td class="num cnt camber">'+c.deferred+'</td>'+
-
 
541
       '<td class="num cnt cmuted">'+(r.transitSecs?bhm(r.transitSecs):'<span style="color:#c5cce0">&mdash;</span>')+'</td>'+
-
 
542
       '<td class="num cnt cmuted">'+(r.inStoreSecs?bhm(r.inStoreSecs):'<span style="color:#c5cce0">&mdash;</span>')+'</td>'+
534
       '<td><span class="pill-state '+stateClass(r.state)+'">'+esc(r.stateLabel)+'</span></td>'+
543
       '<td><span class="pill-state '+stateClass(r.state)+'">'+esc(r.stateLabel)+'</span></td>'+
535
       '<td style="white-space:nowrap" onclick="event.stopPropagation()">'+
544
       '<td style="white-space:nowrap" onclick="event.stopPropagation()">'+
536
         '<button class="actbtn map" title="Live location" onclick="openExec('+A+',\'map\')">&#128205;</button>'+
545
         '<button class="actbtn map" title="Live location" onclick="openExec('+A+',\'map\')">&#128205;</button>'+
537
         '<button class="actbtn view" title="Journey detail" onclick="openExec('+A+')">&#128065;</button>'+
546
         '<button class="actbtn view" title="Journey detail" onclick="openExec('+A+')">&#128065;</button>'+
538
         (CAN_EDIT_BEAT
547
         (CAN_EDIT_BEAT
Line 546... Line 555...
546
// Funnel drill-down: each stage shows the executives who reached it AND the
555
// Funnel drill-down: each stage shows the executives who reached it AND the
547
// drop-off cohort that fell out at this step (the "−N …" sub-line). Filters
556
// drop-off cohort that fell out at this step (the "−N …" sub-line). Filters
548
// mirror the backend funnel conditions exactly (rows carry the per-stage flags).
557
// mirror the backend funnel conditions exactly (rows carry the per-stage flags).
549
var FUNNEL_META = {
558
var FUNNEL_META = {
550
  punchedIn:  {title:'Punched In',   desc:'Started the day (punch-in recorded)', f:function(r){ return r.punchedIn; }},
559
  punchedIn:  {title:'Punched In',   desc:'Started the day (punch-in recorded)', f:function(r){ return r.punchedIn; }},
551
  travelling: {title:'Traveling',    desc:'On the road between stops',           f:function(r){ return r.state==='TRAVELLING' && !r.idleAway; }},
560
  travelling: {title:'Traveling',    desc:'On the road between stops',           f:function(r){ return r.state==='TRAVELLING'; }},
552
  inStore:    {title:'In Store',     desc:'Currently checked in at a stop',      f:function(r){ return r.state==='IN_STORE'; }},
561
  inStore:    {title:'In Store',     desc:'Currently checked in at a stop',      f:function(r){ return r.state==='IN_STORE'; }},
553
  idle:       {title:'Idle / Break', desc:'Between stops, idle off-store beyond the threshold', f:function(r){ return r.state==='TRAVELLING' && r.idleAway; }},
562
  idle:       {title:'Idle / Break', desc:'Punched in but < 1 km covered in the last 15 min', f:function(r){ return r.state==='IDLE'; }},
554
  punchedOut: {title:'Punched Out',  desc:'Ended the day (punch-out recorded)',  f:function(r){ return r.punchedOut; }}
563
  punchedOut: {title:'Punched Out',  desc:'Ended the day (punch-out recorded)',  f:function(r){ return r.punchedOut; }}
555
};
564
};
556
function funnelRows(list, date){
565
function funnelRows(list, date){
557
  if(!list.length) return '<p class="desc">None.</p>';
566
  if(!list.length) return '<p class="desc">None.</p>';
558
  var h='<table><thead><tr><th>Executive / Beat</th><th>Punch In/Out</th><th class="num">Completed</th><th class="num">Planned</th><th>State</th></tr></thead><tbody>';
567
  var h='<table><thead><tr><th>Executive / Beat</th><th>Punch In/Out</th><th class="num">Completed</th><th class="num">Planned</th><th>State</th></tr></thead><tbody>';
Line 662... Line 671...
662
  pModal('<button class="x" onclick="closeModal()">&times;</button><h3>'+esc(title)+'</h3>'+
671
  pModal('<button class="x" onclick="closeModal()">&times;</button><h3>'+esc(title)+'</h3>'+
663
    '<p class="desc">'+esc(desc)+' &middot; '+esc(pRange())+' &middot; click a row for the day-by-day</p>'+pScoreTable(rows));
672
    '<p class="desc">'+esc(desc)+' &middot; '+esc(pRange())+' &middot; click a row for the day-by-day</p>'+pScoreTable(rows));
664
}
673
}
665
function pScoreTable(rows){
674
function pScoreTable(rows){
666
  if(!rows.length) return '<p class="desc">No executives in scope.</p>';
675
  if(!rows.length) return '<p class="desc">No executives in scope.</p>';
-
 
676
  var h='<table><thead><tr><th>Executive</th><th class="num">Plan</th><th class="num">Done</th><th class="num">Adher.</th>'+
-
 
677
    '<th class="num" title="Avg lead visits per active (punched-in) day">Lead/day</th>'+
-
 
678
    '<th class="num" title="Avg franchisee visits per active (punched-in) day">Fr/day</th>'+
667
  var h='<table><thead><tr><th>Executive</th><th class="num">Plan</th><th class="num">Done</th><th class="num">Adher.</th><th class="num">Disc.</th><th class="num">Hrs</th><th class="num">Leads</th><th class="num">Geo&#9888;</th></tr></thead><tbody>';
679
    '<th class="num">Disc.</th><th class="num">Hrs</th><th class="num">Leads</th><th class="num">Geo&#9888;</th></tr></thead><tbody>';
668
  rows.forEach(function(r){
680
  rows.forEach(function(r){
669
    var c=adhColor(r.adherencePct);
681
    var c=adhColor(r.adherencePct);
670
    h+='<tr class="clickable" onclick="openExecPeriod('+r.authUserId+',\''+js(r.name)+'\')">'+
682
    h+='<tr class="clickable" onclick="openExecPeriod('+r.authUserId+',\''+js(r.name)+'\')">'+
671
       '<td><b>'+esc(r.name)+'</b> <span class="lvl">'+esc(r.level)+'</span></td>'+
683
       '<td><span class="lvl">'+esc(r.level)+'</span> <b>'+esc(r.name)+'</b></td>'+
672
       '<td class="num">'+r.planned+'</td><td class="num">'+r.done+'</td>'+
684
       '<td class="num">'+r.planned+'</td><td class="num">'+r.done+'</td>'+
673
       '<td class="num"><b style="color:'+c+'">'+r.adherencePct+'%</b></td>'+
685
       '<td class="num"><b style="color:'+c+'">'+r.adherencePct+'%</b></td>'+
-
 
686
       '<td class="num">'+(r.avgLeadVisitsPerDay||'-')+'</td>'+
-
 
687
       '<td class="num">'+(r.avgFranchiseeVisitsPerDay||'-')+'</td>'+
674
       '<td class="num'+(r.discussionMin>0&&r.discussionMin<10?' warnflag':'')+'">'+(r.discussionMin||'-')+(r.discussionMin?'m':'')+'</td>'+
688
       '<td class="num'+(r.discussionMin>0&&r.discussionMin<10?' warnflag':'')+'">'+(r.discussionMin||'-')+(r.discussionMin?'m':'')+'</td>'+
675
       '<td class="num">'+r.workingHrs+'</td><td class="num">'+r.leads+'</td>'+
689
       '<td class="num">'+r.workingHrs+'</td><td class="num">'+r.leads+'</td>'+
676
       '<td class="num'+(r.geoFlags>0?' badflag':'')+'">'+r.geoFlags+'</td></tr>';
690
       '<td class="num'+(r.geoFlags>0?' badflag':'')+'">'+r.geoFlags+'</td></tr>';
677
  });
691
  });
678
  return h+'</tbody></table>';
692
  return h+'</tbody></table>';