Subversion Repositories SmartDukaan

Rev

Rev 37277 | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 37277 Rev 37343
Line 112... Line 112...
112
  .bdot{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:6px;vertical-align:middle}
112
  .bdot{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:6px;vertical-align:middle}
113
  .ptime{display:inline-block;background:#ebfbee;color:#2f9e44;font-weight:700;font-size:11px;padding:2px 7px;border-radius:6px;font-variant-numeric:tabular-nums}
113
  .ptime{display:inline-block;background:#ebfbee;color:#2f9e44;font-weight:700;font-size:11px;padding:2px 7px;border-radius:6px;font-variant-numeric:tabular-nums}
114
  .potime{display:inline-block;background:#ebfbee;color: #D60303A8;font-weight:700;font-size:11px;padding:2px 7px;border-radius:6px;font-variant-numeric:tabular-nums}
114
  .potime{display:inline-block;background:#ebfbee;color: #D60303A8;font-weight:700;font-size:11px;padding:2px 7px;border-radius:6px;font-variant-numeric:tabular-nums}
115
  td.cnt{font-weight:700;font-variant-numeric:tabular-nums}
115
  td.cnt{font-weight:700;font-variant-numeric:tabular-nums}
116
  .cgreen{color:#2f9e44}.camber{color:#f08c00}.cred{color:#e03131}.cmuted{color:#69748c}
116
  .cgreen{color:#2f9e44}.camber{color:#f08c00}.cred{color:#e03131}.cmuted{color:#69748c}
-
 
117
 
-
 
118
  /* visit-quality flags — top-offenders panel */
-
 
119
  .fsummary{display:flex;gap:24px;flex-wrap:wrap;align-items:flex-end;margin:4px 0 16px}
-
 
120
  .fsummary .fstat .fn{font-size:28px;font-weight:780;letter-spacing:-.02em;line-height:1;font-variant-numeric:tabular-nums}
-
 
121
  .fsummary .fstat .fn small{font-size:15px;color:var(--soft);font-weight:600}
-
 
122
  .fsummary .fstat .fl{font-size:11px;color:var(--muted);font-weight:600;margin-top:6px}
-
 
123
  .fsummary .fstat.sep{border-left:1px solid var(--line);padding-left:24px}
-
 
124
  .fsummary .fstat .ftop{display:flex;align-items:baseline;gap:8px}
-
 
125
  .fpill{display:inline-flex;align-items:center;gap:6px;font-size:10.5px;font-weight:700;padding:3px 9px;border-radius:999px;white-space:nowrap}
-
 
126
  .fpill i{width:6px;height:6px;border-radius:50%;flex:none}
-
 
127
  .fheroes{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
-
 
128
  .fhero{border:1px solid var(--line);border-radius:13px;padding:15px;cursor:pointer;background:linear-gradient(180deg,transparent,color-mix(in srgb,var(--fc) 5%,transparent));transition:border-color .15s}
-
 
129
  .fhero:hover{border-color:var(--fc)}
-
 
130
  .fhtop{display:flex;align-items:center;justify-content:space-between;gap:10px}
-
 
131
  .fhname{font-size:12.5px;font-weight:650;line-height:1.25}
-
 
132
  .fhbig{font-size:36px;font-weight:800;letter-spacing:-.03em;line-height:1;margin-top:12px;font-variant-numeric:tabular-nums}
-
 
133
  .fhsub{font-size:11.5px;color:var(--muted);margin-top:7px}
-
 
134
  .fring{width:44px;height:44px;border-radius:50%;flex:none;display:flex;align-items:center;justify-content:center;-webkit-mask:radial-gradient(circle 14px at center,transparent 98%,#000 100%);mask:radial-gradient(circle 14px at center,transparent 98%,#000 100%)}
-
 
135
  .frlbl{font-size:10px;font-weight:800;font-variant-numeric:tabular-nums}
-
 
136
  .fsub{font-size:11px;letter-spacing:.09em;text-transform:uppercase;color:var(--soft);font-weight:700;margin:16px 2px 6px}
-
 
137
  .fsub.muted{opacity:.7}
-
 
138
  .frow{display:grid;grid-template-columns:28px minmax(140px,1fr) 48px 66px 96px;align-items:center;gap:12px;padding:9px 8px;border-radius:9px}
-
 
139
  .frow.clickable{cursor:pointer}
-
 
140
  .frow.clickable:hover{background:#f7f9fd}
-
 
141
  .frow.quiet{opacity:.6}
-
 
142
  .fnum{font-size:11px;font-weight:700;color:var(--soft);text-align:center;font-variant-numeric:tabular-nums}
-
 
143
  .frname{font-size:13px;font-weight:600}
-
 
144
  .frrate{font-size:13.5px;font-weight:750;text-align:right;font-variant-numeric:tabular-nums}
-
 
145
  .frfrac{font-size:12px;color:var(--soft);text-align:right;font-variant-numeric:tabular-nums}
-
 
146
  @media (max-width:900px){.fheroes{grid-template-columns:1fr}.frow{grid-template-columns:24px 1fr 48px 96px}.frfrac{display:none}}
-
 
147
  .bkbtn{position:absolute;top:14px;left:16px;background:none;border:0;color:var(--muted);font:inherit;font-size:13px;font-weight:600;cursor:pointer;padding:4px 8px;border-radius:7px}
-
 
148
  .bkbtn:hover{background:#eef1fb;color:var(--brand)}
-
 
149
 
-
 
150
  /* orders list — level filter chips */
-
 
151
  .olhead{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:2px}
-
 
152
  .olchips{display:inline-flex;gap:3px;background:#eef1fb;border-radius:9px;padding:3px}
-
 
153
  .olchip{appearance:none;border:0;background:transparent;color:var(--muted);font:inherit;font-size:12px;font-weight:650;padding:5px 12px;border-radius:7px;cursor:pointer}
-
 
154
  .olchip:hover{color:var(--ink)}
-
 
155
  .olchip.on{background:var(--brand);color:#fff}
117
  tr.boardtot td{background:#f4f6fb;border-bottom:2px solid #cdd3df;font-weight:700}
156
  tr.boardtot td{background:#f4f6fb;border-bottom:2px solid #cdd3df;font-weight:700}
118
  .actbtn{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:7px;border:0;cursor:pointer;color:#fff;font-size:12px;margin-left:3px}
157
  .actbtn{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:7px;border:0;cursor:pointer;color:#fff;font-size:12px;margin-left:3px}
119
  .actbtn.map{background:#12b886}.actbtn.view{background:#3b5bdb}
158
  .actbtn.map{background:#12b886}.actbtn.view{background:#3b5bdb}
120
  .actbtn.edit{background:#f08c00}.actbtn.add{background:#2f9e44;font-size:15px}
159
  .actbtn.edit{background:#f08c00}.actbtn.add{background:#2f9e44;font-size:15px}
121
  .actbtn:active{transform:translateY(1px)}
160
  .actbtn:active{transform:translateY(1px)}
Line 267... Line 306...
267
  .tlchip{font-size:10.5px;font-weight:600;color:var(--muted);background:#f1f3f9;border-radius:6px;padding:2px 7px;white-space:nowrap}
306
  .tlchip{font-size:10.5px;font-weight:600;color:var(--muted);background:#f1f3f9;border-radius:6px;padding:2px 7px;white-space:nowrap}
268
  .tlbadge{font-size:10.5px;font-weight:700;border:0;border-radius:6px;padding:2px 8px;white-space:nowrap;cursor:pointer;line-height:1.5}
307
  .tlbadge{font-size:10.5px;font-weight:700;border:0;border-radius:6px;padding:2px 8px;white-space:nowrap;cursor:pointer;line-height:1.5}
269
  .tlbadge.ord{background:#e7f0ff;color:#1c56c9}
308
  .tlbadge.ord{background:#e7f0ff;color:#1c56c9}
270
  .tlbadge.ord:hover{background:#d4e4ff}
309
  .tlbadge.ord:hover{background:#d4e4ff}
271
  .tlbadge.sch{background:#fff4e6;color:#c25709;cursor:default}
310
  .tlbadge.sch{background:#fff4e6;color:#c25709;cursor:default}
-
 
311
  .tlbadge.dwell{background:#fff4e6;color:#e8590c;cursor:default}
-
 
312
  .tlbadge.nobill{background:#fff0f0;color:#e03131;cursor:default}
-
 
313
  .tlcard.long-dwell{border-color:#ffd8a8;box-shadow:0 2px 12px rgba(232,89,12,.16)}
272
  .exectabs{display:flex;gap:6px;justify-content:center;margin:12px 0 4px}
314
  .exectabs{display:flex;gap:6px;justify-content:center;margin:12px 0 4px}
273
  .exectab{padding:6px 14px;border-radius:8px;font-size:12.5px;font-weight:650;color:var(--muted);cursor:pointer;border:1px solid var(--line);background:#fff}
315
  .exectab{padding:6px 14px;border-radius:8px;font-size:12.5px;font-weight:650;color:var(--muted);cursor:pointer;border:1px solid var(--line);background:#fff}
274
  .exectab.active{background:var(--brand);color:#fff;border-color:var(--brand)}
316
  .exectab.active{background:var(--brand);color:#fff;border-color:var(--brand)}
275
  #bjMap{width:100%;height:460px;border-radius:12px;border:1px solid var(--line);margin-top:10px;background:#eef1f7;overflow:hidden}
317
  #bjMap{width:100%;height:460px;border-radius:12px;border:1px solid var(--line);margin-top:10px;background:#eef1f7;overflow:hidden}
276
  .bjlegend{display:flex;gap:16px;align-items:center;flex-wrap:wrap;margin-top:12px;padding:10px 12px;border:1px solid var(--line);border-radius:10px;background:#f8f9fc;font-size:12.5px;min-height:20px}
318
  .bjlegend{display:flex;gap:16px;align-items:center;flex-wrap:wrap;margin-top:12px;padding:10px 12px;border:1px solid var(--line);border-radius:10px;background:#f8f9fc;font-size:12.5px;min-height:20px}
Line 520... Line 562...
520
  h+='</div></div>';
562
  h+='</div></div>';
521
 
563
 
522
  window._boardFilter = null;             // funnel-card filter (null = all rows)
564
  window._boardFilter = null;             // funnel-card filter (null = all rows)
523
  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>';
565
  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>';
524
 
566
 
-
 
567
  window._ordersList = d.ordersList || [];
-
 
568
  h+='<div class="sectionlabel">Billing &middot; Orders today</div><div id="bj-orders">'+ordersCard()+'</div>';
-
 
569
 
525
  window._visitFlags = d.visitFlags || [];
570
  window._visitFlags = d.visitFlags || [];
526
  h+='<div class="sectionlabel">Visit-quality flags</div>';
571
  h+='<div class="sectionlabel">Visit-quality flags</div>';
527
  h+='<div class="card"><h2>Visit-quality flags</h2><p class="desc">Every rule scored on today\'s visits. Click a row with hits to see the flagged visits.</p>'+flagsTable(window._visitFlags)+'</div>';
572
  h+='<div class="card"><h2>Visit-quality flags</h2><p class="desc">Today\'s biggest quality gaps first. Click a rule to see the flagged visits.</p>'+flagsTable(window._visitFlags)+'</div>';
528
 
573
 
529
  el('bj-content').innerHTML=h;
574
  el('bj-content').innerHTML=h;
530
}
575
}
531
function stateClass(st){ return st==='IN_STORE'?'ps-store':st==='TRAVELLING'?'ps-travel':st==='DONE'?'ps-done':st==='NOT_STARTED'?'ps-no':'ps-idle'; }
576
function stateClass(st){ return st==='IN_STORE'?'ps-store':st==='TRAVELLING'?'ps-travel':st==='DONE'?'ps-done':st==='NOT_STARTED'?'ps-no':'ps-idle'; }
532
function js(s){ return String(s==null?'':s).replace(/\\/g,'\\\\').replace(/'/g,"\\'"); }
577
function js(s){ return String(s==null?'':s).replace(/\\/g,'\\\\').replace(/'/g,"\\'"); }
Line 580... Line 625...
580
  var total=Math.max(r.planned||0, completed+deferred);
625
  var total=Math.max(r.planned||0, completed+deferred);
581
  return {total:total, completed:completed, deferred:deferred, pending:total-completed-deferred};
626
  return {total:total, completed:completed, deferred:deferred, pending:total-completed-deferred};
582
}
627
}
583
// seconds -> compact "1h 20m" / "45m" for the board's time columns.
628
// seconds -> compact "1h 20m" / "45m" for the board's time columns.
584
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'); }
629
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'); }
-
 
630
// Day-counts for the board's Onboarded / Last-bill columns. null -> em dash.
-
 
631
function daysAgo(n){ return (n==null) ? '<span style="color:#c5cce0">&mdash;</span>' : (n<=0?'today':n+'d'); }
-
 
632
// Last-billing recency, coloured by staleness (fresher = greener, stale = red).
-
 
633
function billDaysHtml(n){
-
 
634
  if(n==null) return '<span style="color:#c5cce0">&mdash;</span>';
-
 
635
  var c = n>60?'#e03131':(n>30?'#e8590c':'#2f9e44');
-
 
636
  return '<span style="color:'+c+'">'+(n<=0?'today':n+'d')+'</span>';
-
 
637
}
585
// Click-to-sort for the live board (mirrors the Period scorecard's sortCard). The
638
// Click-to-sort for the live board (mirrors the Period scorecard's sortCard). The
586
// numeric columns are derived (boardCounts) and times are raw seconds, so we sort on
639
// numeric columns are derived (boardCounts) and times are raw seconds, so we sort on
587
// the underlying values here — not the rendered "1h 20m"/count strings.
640
// the underlying values here — not the rendered "1h 20m"/count strings.
588
var boardSortKey=null, boardSortAsc=false;
641
var boardSortKey=null, boardSortAsc=false;
589
function boardSortVal(r, key){
642
function boardSortVal(r, key){
Line 594... Line 647...
594
    case 'total': case 'completed': case 'pending': case 'deferred':
647
    case 'total': case 'completed': case 'pending': case 'deferred':
595
                    return boardCounts(r)[key];
648
                    return boardCounts(r)[key];
596
    case 'transit': return r.transitSecs||0;
649
    case 'transit': return r.transitSecs||0;
597
    case 'inStore': return r.inStoreSecs||0;
650
    case 'inStore': return r.inStoreSecs||0;
598
    case 'state':   return r.stateLabel||'';
651
    case 'state':   return r.stateLabel||'';
-
 
652
    case 'onboard': return r.onboardDays==null?1e9:r.onboardDays;
-
 
653
    case 'bill':    return r.lastBillDays==null?1e9:r.lastBillDays;
599
    default:        return 0;
654
    default:        return 0;
600
  }
655
  }
601
}
656
}
602
function sortBoard(key){
657
function sortBoard(key){
603
  if(boardSortKey===key) boardSortAsc=!boardSortAsc; else { boardSortKey=key; boardSortAsc=false; }
658
  if(boardSortKey===key) boardSortAsc=!boardSortAsc; else { boardSortKey=key; boardSortAsc=false; }
Line 615... Line 670...
615
    '<th class="num" onclick="sortBoard(\'completed\')">Completed'+bsArrow('completed')+'</th>'+
670
    '<th class="num" onclick="sortBoard(\'completed\')">Completed'+bsArrow('completed')+'</th>'+
616
    '<th class="num" onclick="sortBoard(\'pending\')">Pending'+bsArrow('pending')+'</th>'+
671
    '<th class="num" onclick="sortBoard(\'pending\')">Pending'+bsArrow('pending')+'</th>'+
617
    '<th class="num" onclick="sortBoard(\'deferred\')">Deferred'+bsArrow('deferred')+'</th>'+
672
    '<th class="num" onclick="sortBoard(\'deferred\')">Deferred'+bsArrow('deferred')+'</th>'+
618
    '<th class="num" title="Transit time between stops so far today" onclick="sortBoard(\'transit\')">Transit'+bsArrow('transit')+'</th>'+
673
    '<th class="num" title="Transit time between stops so far today" onclick="sortBoard(\'transit\')">Transit'+bsArrow('transit')+'</th>'+
619
    '<th class="num" title="Time spent inside stores so far today" onclick="sortBoard(\'inStore\')">In-store'+bsArrow('inStore')+'</th>'+
674
    '<th class="num" title="Time spent inside stores so far today" onclick="sortBoard(\'inStore\')">In-store'+bsArrow('inStore')+'</th>'+
620
    '<th onclick="sortBoard(\'state\')">State'+bsArrow('state')+'</th><th>Action</th>'+
675
    '<th onclick="sortBoard(\'state\')">State'+bsArrow('state')+'</th>'+
-
 
676
    '<th class="num" title="Days since this exec last onboarded a new partner" onclick="sortBoard(\'onboard\')">Onboarded'+bsArrow('onboard')+'</th>'+
-
 
677
    '<th class="num" title="Days since this exec\'s partners last billed" onclick="sortBoard(\'bill\')">Last bill'+bsArrow('bill')+'</th>'+
-
 
678
    '<th>Action</th>'+
621
    '</tr></thead><tbody>';
679
    '</tr></thead><tbody>';
622
  // column totals, computed up front so the Total row can lead the table
680
  // column totals, computed up front so the Total row can lead the table
623
  var t={total:0,completed:0,pending:0,deferred:0,transit:0,inStore:0};
681
  var t={total:0,completed:0,pending:0,deferred:0,transit:0,inStore:0};
624
  rows.forEach(function(r){
682
  rows.forEach(function(r){
625
    var c=boardCounts(r);
683
    var c=boardCounts(r);
Line 631... Line 689...
631
     '<td class="num cnt cmuted">'+t.total+'</td>'+
689
     '<td class="num cnt cmuted">'+t.total+'</td>'+
632
     '<td class="num cnt cgreen">'+t.completed+'</td>'+
690
     '<td class="num cnt cgreen">'+t.completed+'</td>'+
633
     '<td class="num cnt">'+t.pending+'</td>'+
691
     '<td class="num cnt">'+t.pending+'</td>'+
634
     '<td class="num cnt camber">'+t.deferred+'</td>'+
692
     '<td class="num cnt camber">'+t.deferred+'</td>'+
635
     '<td class="num cnt cmuted">'+bhm(t.transit)+'</td>'+
693
     '<td class="num cnt cmuted">'+bhm(t.transit)+'</td>'+
636
     '<td class="num cnt cmuted">'+bhm(t.inStore)+'</td><td></td><td></td>'+
694
     '<td class="num cnt cmuted">'+bhm(t.inStore)+'</td><td></td><td></td><td></td><td></td>'+
637
     '</tr>';
695
     '</tr>';
638
  rows.forEach(function(r){
696
  rows.forEach(function(r){
639
    var rowcls = r.state==='NOT_STARTED' ? 'rowbad' : (r.idleAway?'rowwarn':'');
697
    var rowcls = r.state==='NOT_STARTED' ? 'rowbad' : (r.idleAway?'rowwarn':'');
640
    var A = r.userId+",'"+js(r.name)+"','"+date+"'";   // openExec args
698
    var A = r.userId+",'"+js(r.name)+"','"+date+"'";   // openExec args
641
    var go = 'onclick="openExec('+A+')"';
699
    var go = 'onclick="openExec('+A+')"';
Line 650... Line 708...
650
       '<td class="num cnt">'+c.pending+'</td>'+
708
       '<td class="num cnt">'+c.pending+'</td>'+
651
       '<td class="num cnt camber">'+c.deferred+'</td>'+
709
       '<td class="num cnt camber">'+c.deferred+'</td>'+
652
       '<td class="num cnt cmuted">'+(r.transitSecs?bhm(r.transitSecs):'<span style="color:#c5cce0">&mdash;</span>')+'</td>'+
710
       '<td class="num cnt cmuted">'+(r.transitSecs?bhm(r.transitSecs):'<span style="color:#c5cce0">&mdash;</span>')+'</td>'+
653
       '<td class="num cnt cmuted">'+(r.inStoreSecs?bhm(r.inStoreSecs):'<span style="color:#c5cce0">&mdash;</span>')+'</td>'+
711
       '<td class="num cnt cmuted">'+(r.inStoreSecs?bhm(r.inStoreSecs):'<span style="color:#c5cce0">&mdash;</span>')+'</td>'+
654
       '<td><span class="pill-state '+stateClass(r.state)+'">'+esc(r.stateLabel)+'</span></td>'+
712
       '<td><span class="pill-state '+stateClass(r.state)+'">'+esc(r.stateLabel)+'</span></td>'+
-
 
713
       '<td class="num cnt cmuted">'+daysAgo(r.onboardDays)+'</td>'+
-
 
714
       '<td class="num cnt">'+billDaysHtml(r.lastBillDays)+'</td>'+
655
       '<td style="white-space:nowrap" onclick="event.stopPropagation()">'+
715
       '<td style="white-space:nowrap" onclick="event.stopPropagation()">'+
656
         '<button class="actbtn map" title="Live location" onclick="openExec('+A+',\'map\')">&#128205;</button>'+
716
         '<button class="actbtn map" title="Live location" onclick="openExec('+A+',\'map\')">&#128205;</button>'+
657
         '<button class="actbtn view" title="Journey detail" onclick="openExec('+A+')">&#128065;</button>'+
717
         '<button class="actbtn view" title="Journey detail" onclick="openExec('+A+')">&#128065;</button>'+
658
         (CAN_EDIT_BEAT
718
         (CAN_EDIT_BEAT
659
           ? '<button class="actbtn edit" title="Edit beat in Beat Planner" onclick="openPlanner('+r.authUserId+','+(r.beatId||0)+',\''+date+'\',\'edit\')">&#9998;</button>'+
719
           ? '<button class="actbtn edit" title="Edit beat in Beat Planner" onclick="openPlanner('+r.authUserId+','+(r.beatId||0)+',\''+date+'\',\'edit\')">&#9998;</button>'+
Line 926... Line 986...
926
  idleStale:       {bg:'#fff4e6', fg:'#e8590c', ic:'&#9201;',   title:'Idle &mdash; off-store &gt; 59 min', sub:'stationary &gt;20 m from any store',  cls:'warnflag'},
986
  idleStale:       {bg:'#fff4e6', fg:'#e8590c', ic:'&#9201;',   title:'Idle &mdash; off-store &gt; 59 min', sub:'stationary &gt;20 m from any store',  cls:'warnflag'},
927
  geofence:        {bg:'#fff0f0', fg:'#e03131', ic:'&#128205;', title:'Check-in outside store geofence',  sub:'&gt;50 m from partner\'s saved location', cls:'badflag'},
987
  geofence:        {bg:'#fff0f0', fg:'#e03131', ic:'&#128205;', title:'Check-in outside store geofence',  sub:'&gt;50 m from partner\'s saved location', cls:'badflag'},
928
  locationOff:     {bg:'#fff4e6', fg:'#e8590c', ic:'&#128244;', title:'Location turned off mid-journey',  sub:'GPS toggled off',                     cls:'warnflag'},
988
  locationOff:     {bg:'#fff4e6', fg:'#e8590c', ic:'&#128244;', title:'Location turned off mid-journey',  sub:'GPS toggled off',                     cls:'warnflag'},
929
  deferredAwaiting:{bg:'#eef1f6', fg:'#69748c', ic:'&#8635;',   title:'Deferred, awaiting re-action',     sub:'shops deferred today',                cls:''}
989
  deferredAwaiting:{bg:'#eef1f6', fg:'#69748c', ic:'&#8635;',   title:'Deferred, awaiting re-action',     sub:'shops deferred today',                cls:''}
930
};
990
};
931
// ---- Visit-quality flags: a today-scoped replica of the 14-row monitor table ----
991
// ---- Billing: today's orders, filterable by the executive's org level (L1..L5) ----
932
// higher rate = worse, so redder as the flagged share climbs.
992
window._ordersLevel = window._ordersLevel || '';   // '' = all levels
933
function flagRateColor(p){ if(p>=50)return '#e03131'; if(p>=25)return '#e8590c'; if(p>=10)return '#f08c00'; return '#2f9e44'; }
993
function setOrdersLevel(L){ window._ordersLevel = (window._ordersLevel===L)?'':L; var c=el('bj-orders'); if(c) c.innerHTML=ordersCard(); }
-
 
994
// Compact Indian-currency: ₹7.8L / ₹1.2Cr / ₹9.5k.
-
 
995
function inr(n){ n=Math.round(n||0);
-
 
996
  if(n>=10000000) return '&#8377;'+(n/10000000).toFixed(2).replace(/\.00$/,'')+'Cr';
-
 
997
  if(n>=100000)   return '&#8377;'+(n/100000).toFixed(2).replace(/\.00$/,'')+'L';
-
 
998
  if(n>=1000)     return '&#8377;'+(n/1000).toFixed(1).replace(/\.0$/,'')+'k';
-
 
999
  return '&#8377;'+n;
-
 
1000
}
-
 
1001
// Today's orders grouped by partner (respecting the level filter), sorted by ₹ total.
-
 
1002
function orderGroups(){
-
 
1003
  var list=window._ordersList||[];
-
 
1004
  var view = window._ordersLevel ? list.filter(function(o){return o.level===window._ordersLevel;}) : list;
-
 
1005
  var groups={}, order=[];
-
 
1006
  view.forEach(function(o){
-
 
1007
    var k = o.fofoId || ('p:'+o.exec+'|'+o.partner);
-
 
1008
    if(!groups[k]){ groups[k]={exec:o.exec,level:o.level,partner:o.partner,fofoId:o.fofoId,count:0,total:0,last:''}; order.push(k); }
-
 
1009
    var g=groups[k]; g.count++; g.total+=(o.amount||0);
-
 
1010
    if((o.time||'')>g.last) g.last=o.time||'';
-
 
1011
  });
-
 
1012
  var glist=order.map(function(k){return groups[k];}).sort(function(a,b){return b.total-a.total;});
-
 
1013
  return {glist:glist, view:view, total:view.reduce(function(a,o){return a+(o.amount||0);},0)};
-
 
1014
}
-
 
1015
function ordersCard(){
-
 
1016
  var levels=['','L1','L2','L3','L4','L5'];
-
 
1017
  var chips=levels.map(function(L){
-
 
1018
    var on=(window._ordersLevel===L);
-
 
1019
    return '<button class="olchip'+(on?' on':'')+'" onclick="setOrdersLevel(\''+L+'\')">'+(L||'All')+'</button>';
-
 
1020
  }).join('');
-
 
1021
  var h='<div class="card"><div class="olhead"><h2>Orders today</h2><div class="olchips">'+chips+'</div></div>';
-
 
1022
  if(!(window._ordersList||[]).length) return h+'<p class="desc">No orders placed today.</p></div>';
-
 
1023
  var g=orderGroups(), glist=g.glist;
-
 
1024
  if(!glist.length) return h+'<p class="desc">No orders at '+esc(window._ordersLevel)+' today.</p></div>';
-
 
1025
  var top=glist.slice(0,3);
-
 
1026
  h+='<div class="fsummary">'+
-
 
1027
    '<div class="fstat"><div class="fn">'+inr(g.total)+'</div><div class="fl">Billed today</div></div>'+
-
 
1028
    '<div class="fstat sep"><div class="fn">'+g.view.length+'</div><div class="fl">Orders</div></div>'+
-
 
1029
    '<div class="fstat sep"><div class="fn">'+glist.length+'</div><div class="fl">Partners billed</div></div>'+
-
 
1030
    '</div>';
-
 
1031
  h+='<div class="fheroes">'; top.forEach(function(gr){ h+=orderHero(gr, g.total); }); h+='</div>';
-
 
1032
  h+='<p class="desc" style="margin-top:12px"><a href="#" onclick="openOrdersTable();return false;">View all '+glist.length+' billed partners &rarr;</a></p>';
-
 
1033
  return h+'</div>';
-
 
1034
}
-
 
1035
function orderHero(g, grand){
-
 
1036
  var pct = grand>0 ? Math.round(100*g.total/grand) : 0, c='var(--brand)';
-
 
1037
  return '<div class="fhero clickable" onclick="openOrdersTable()" style="--fc:'+c+'">'+
-
 
1038
    '<div class="fhtop"><div class="fhname">'+esc(g.partner)+'</div>'+
-
 
1039
      '<span class="fring" style="background:conic-gradient('+c+' '+(pct*3.6)+'deg, var(--line2) 0)"><span class="frlbl" style="color:'+c+'">'+pct+'%</span></span></div>'+
-
 
1040
    '<div class="fhbig" style="color:'+c+'">'+inr(g.total)+'</div>'+
-
 
1041
    '<div class="fhsub">'+g.count+' order'+(g.count>1?'s':'')+' &middot; <span class="lvl">'+esc(g.level||'-')+'</span> '+esc(g.exec)+'</div>'+
-
 
1042
    '</div>';
-
 
1043
}
-
 
1044
// Full grouped orders table in a popup; each partner row opens its order detail.
-
 
1045
function openOrdersTable(){
-
 
1046
  var g=orderGroups(), glist=g.glist, date=window._boardDate||'';
-
 
1047
  var h='<button class="x" onclick="closeModal()">&times;</button><h3>Orders today</h3>'+
-
 
1048
    '<p class="desc">'+g.view.length+' orders from '+glist.length+' partners &middot; '+inr(g.total)+(window._ordersLevel?' &middot; '+esc(window._ordersLevel)+' only':'')+' &middot; click a partner for its orders</p>'+
-
 
1049
    '<div style="overflow:auto;max-height:64vh"><table><thead><tr><th>Executive</th><th>Partner</th><th class="num">Orders</th><th class="num">Amount</th><th class="num">Last</th></tr></thead><tbody>';
-
 
1050
  glist.forEach(function(gr){
-
 
1051
    var go = gr.fofoId ? ' class="clickable" onclick="bjOpenOrderList('+gr.fofoId+",'"+date+"')\"" : '';
-
 
1052
    h+='<tr'+go+'><td><span class="lvl">'+esc(gr.level||'-')+'</span> <b>'+esc(gr.exec)+'</b></td>'+
-
 
1053
       '<td>'+esc(gr.partner)+'</td>'+
-
 
1054
       '<td class="num cnt cmuted">'+gr.count+'</td>'+
-
 
1055
       '<td class="num cnt">&#8377;'+num(Math.round(gr.total))+'</td>'+
-
 
1056
       '<td class="num cmuted">'+esc((gr.last||'').slice(0,5))+'</td></tr>';
-
 
1057
  });
-
 
1058
  h+='</tbody></table></div>';
-
 
1059
  el('bj-modal').innerHTML=h; el('bj-modal-back').classList.add('show');
-
 
1060
}
-
 
1061
 
-
 
1062
// ---- Visit-quality flags: "top offenders" panel (severity-first, dashboard palette) ----
-
 
1063
// Internal tuning status -> display severity {label, color}, using the dashboard's own tokens.
934
function flagStatusColor(s){
1064
function flagSeverity(status){
935
  switch(s){
1065
  switch(status){
936
    case 'action':      return '#e03131';
1066
    case 'action':      return {label:'Action',  color:'var(--red)'};
937
    case 'systemic':    return '#e8590c';
1067
    case 'systemic':    return {label:'High',    color:'var(--orange)'};
938
    case 'partial':     return '#f08c00';
1068
    case 'partial':     return {label:'High',    color:'var(--orange)'};
939
    case 'needs floor': return '#1098ad';
1069
    case 'needs floor': return {label:'Watch',   color:'var(--cyan)'};
940
    case 'too broad':   return '#7048e8';
1070
    case 'too broad':   return {label:'Monitor', color:'var(--violet)'};
941
    case 'contained':   return '#2f9e44';
1071
    case 'contained':   return {label:'Monitor', color:'var(--violet)'};
942
    default:            return '#97a1b5';   // "never fires" / "no data"
1072
    default:            return {label:'No data', color:'var(--soft)'};   // no data / never fires
943
  }
1073
  }
944
}
1074
}
-
 
1075
function flagStatusColor(s){ return flagSeverity(s).color; }
945
function flagStatusChip(s){
1076
function flagStatusChip(s){
946
  var c=flagStatusColor(s);
1077
  var v=flagSeverity(s);
947
  return '<span style="display:inline-block;font-size:10.5px;font-weight:700;padding:2px 8px;border-radius:999px;background:'+c+'1a;color:'+c+'">'+esc(s)+'</span>';
1078
  return '<span class="fpill" style="color:'+v.color+';background:color-mix(in srgb,'+v.color+' 13%,transparent)"><i style="background:'+v.color+'"></i>'+esc(v.label)+'</span>';
-
 
1079
}
-
 
1080
function frate(r){ return r.denom>0 ? Math.round(100*r.count/r.denom) : 0; }
-
 
1081
function ffiring(r){ return (r.count||0)>0; }
-
 
1082
function flagRing(r){
-
 
1083
  var p=frate(r), c=flagStatusColor(r.status);
-
 
1084
  return '<span class="fring" style="background:conic-gradient('+c+' '+(p*3.6)+'deg, var(--line2) 0)"><span class="frlbl" style="color:'+c+'">'+p+'%</span></span>';
-
 
1085
}
-
 
1086
function flagHero(r){
-
 
1087
  var c=flagStatusColor(r.status);
-
 
1088
  return '<div class="fhero clickable" onclick="openFlag(\''+r.code+'\')" style="--fc:'+c+'">'+
-
 
1089
    '<div class="fhtop"><div class="fhname">'+esc(r.title)+'</div>'+flagRing(r)+'</div>'+
-
 
1090
    '<div class="fhbig" style="color:'+c+'">'+num(r.count)+'</div>'+
-
 
1091
    '<div class="fhsub">of '+num(r.denom)+' &middot; '+flagStatusChip(r.status)+'</div>'+
-
 
1092
    '</div>';
-
 
1093
}
-
 
1094
function flagRow(r, quiet){
-
 
1095
  var c=flagStatusColor(r.status), p=frate(r);
-
 
1096
  var attr = quiet ? ' class="frow quiet"' : ' class="frow clickable" onclick="openFlag(\''+r.code+'\')"';
-
 
1097
  return '<div'+attr+'>'+
-
 
1098
    '<div class="fnum">'+esc(r.num)+'</div>'+
-
 
1099
    '<div class="frname">'+esc(r.title)+'</div>'+
-
 
1100
    '<div class="frrate" style="color:'+(ffiring(r)?c:'var(--soft)')+'">'+(r.denom?p+'%':'&mdash;')+'</div>'+
-
 
1101
    '<div class="frfrac">'+(r.denom?num(r.count)+' / '+num(r.denom):'&mdash;')+'</div>'+
-
 
1102
    '<div>'+flagStatusChip(r.status)+'</div>'+
-
 
1103
    '</div>';
948
}
1104
}
949
function flagsTable(list){
1105
function flagsTable(list){
950
  if(!list||!list.length) return '<p class="desc">No flag data for this date.</p>';
1106
  if(!list||!list.length) return '<p class="desc">No flag data for this date.</p>';
951
  var h='<div style="overflow-x:auto"><table><thead><tr>'+
-
 
952
    '<th style="width:34px">#</th><th>Rule</th><th class="num">Flagged</th><th class="num">Of</th><th class="num">Rate</th><th>Status</th>'+
-
 
953
    '</tr></thead><tbody>';
1107
  var fires=list.filter(ffiring);
954
  list.forEach(function(r){
1108
  var flagged=list.reduce(function(a,r){return a+(r.count||0);},0);
955
    var hits=r.count||0, denom=r.denom||0, rate=pct(hits,denom);
1109
  var top=fires.slice().sort(function(a,b){return (b.count||0)-(a.count||0);}).slice(0,3);
956
    var open = hits>0 ? ' class="clickable" onclick="openFlag(\''+r.code+'\')"' : '';
1110
  var topK={}; top.forEach(function(r){topK[r.code]=1;});
957
    var cntColor = hits>0 ? flagRateColor(rate) : '#97a1b5';
1111
  var rest=fires.filter(function(r){return !topK[r.code];}).sort(function(a,b){return frate(b)-frate(a);});
958
    h+='<tr'+open+'>'+
1112
  var quiet=list.filter(function(r){return !ffiring(r);});
959
       '<td style="font-weight:700;color:#97a1b5">'+esc(r.num)+'</td>'+
1113
  var worst=fires.slice().sort(function(a,b){return frate(b)-frate(a);})[0];
-
 
1114
 
960
       '<td>'+esc(r.title)+'</td>'+
1115
  var h='<div class="fsummary">'+
961
       '<td class="num cnt" style="font-weight:700;color:'+cntColor+'">'+num(hits)+'</td>'+
1116
    '<div class="fstat"><div class="fn">'+num(flagged)+'</div><div class="fl">Flagged visits today</div></div>'+
962
       '<td class="num cnt cmuted">'+num(denom)+'</td>'+
-
 
963
       '<td class="num cnt cmuted">'+(denom?rate+'%':'&mdash;')+'</td>'+
1117
    '<div class="fstat sep"><div class="fn">'+fires.length+' <small>/ '+list.length+'</small></div><div class="fl">Rules firing</div></div>'+
964
       '<td>'+flagStatusChip(r.status)+(hits>0?' <span class="chev">&#9656;</span>':'')+'</td>'+
1118
    (worst?'<div class="fstat sep"><div class="ftop"><div class="fn" style="color:'+flagStatusColor(worst.status)+'">'+frate(worst)+'%</div>'+flagStatusChip(worst.status)+'</div><div class="fl">Worst rule &middot; #'+esc(worst.num)+' '+esc(worst.title)+'</div></div>':'')+
965
       '</tr>';
1119
    '</div>';
966
  });
1120
 
-
 
1121
  h+='<div class="fheroes">'; top.forEach(function(r){ h+=flagHero(r); }); h+='</div>';
-
 
1122
  if(rest.length){ h+='<div class="fsub">Other firing rules</div><div class="frows">'; rest.forEach(function(r){ h+=flagRow(r,false); }); h+='</div>'; }
-
 
1123
  if(quiet.length){ h+='<div class="fsub muted">Quiet today</div><div class="frows">'; quiet.forEach(function(r){ h+=flagRow(r,true); }); h+='</div>'; }
967
  return h+'</tbody></table></div>';
1124
  return h;
968
}
1125
}
969
// Minutes-of-day from the first HH:MM in a flag detail string (-1 if none) — for sorting.
1126
// Minutes-of-day from the first HH:MM in a flag detail string (-1 if none) — for sorting.
970
function flagTimeVal(detail){
1127
function flagTimeVal(detail){
971
  var m=String(detail||'').match(/(\d{1,2}):(\d{2})/);
1128
  var m=String(detail||'').match(/(\d{1,2}):(\d{2})/);
972
  return m ? (parseInt(m[1],10)*60+parseInt(m[2],10)) : -1;
1129
  return m ? (parseInt(m[1],10)*60+parseInt(m[2],10)) : -1;
Line 986... Line 1143...
986
  if(!items.length){ h+='<p class="desc">Nothing flagged today.</p>'; }
1143
  if(!items.length){ h+='<p class="desc">Nothing flagged today.</p>'; }
987
  else{
1144
  else{
988
    h+='<p class="desc">Click a row to open that executive\'s journey detail.</p><table><tbody>';
1145
    h+='<p class="desc">Click a row to open that executive\'s journey detail.</p><table><tbody>';
989
    items.forEach(function(x){
1146
    items.forEach(function(x){
990
      var canOpen=x.userId>0;
1147
      var canOpen=x.userId>0;
991
      var go=canOpen?' class="clickable" onclick="openExec('+x.userId+",'"+js(x.name)+"','"+date+"')\"":'';
1148
      var go=canOpen?' class="clickable" onclick="openExec('+x.userId+",'"+js(x.name)+"','"+date+"','timeline','flag:"+r.code+"')\"":'';
992
      h+='<tr'+go+'><td style="font-weight:600;white-space:nowrap">'+esc(x.name)+'</td>'+
1149
      h+='<tr'+go+'><td style="font-weight:600;white-space:nowrap">'+esc(x.name)+'</td>'+
993
         '<td style="color:#69748c">'+esc(x.detail||'')+'</td></tr>';
1150
         '<td style="color:#69748c">'+esc(x.detail||'')+'</td></tr>';
994
    });
1151
    });
995
    h+='</tbody></table>';
1152
    h+='</tbody></table>';
996
  }
1153
  }
Line 1023... Line 1180...
1023
         '<td style="color:#69748c">'+esc(item?item.detail:'')+'</td></tr>';
1180
         '<td style="color:#69748c">'+esc(item?item.detail:'')+'</td></tr>';
1024
    });
1181
    });
1025
    h+='</tbody></table>';
1182
    h+='</tbody></table>';
1026
  }
1183
  }
1027
  if(userId>0){
1184
  if(userId>0){
1028
    h+='<p class="desc" style="margin-top:10px"><a href="#" onclick="closeModal();openExec('+userId+',\''+js(name)+'\',\''+date+'\');return false;">Open journey detail &rarr;</a></p>';
1185
    h+='<p class="desc" style="margin-top:10px"><a href="#" onclick="openExec('+userId+',\''+js(name)+'\',\''+date+'\',\'timeline\',\'execflags\');return false;">Open journey detail &rarr;</a></p>';
1029
  }
1186
  }
1030
  el('bj-modal').innerHTML=h; el('bj-modal-back').classList.add('show');
1187
  el('bj-modal').innerHTML=h; el('bj-modal-back').classList.add('show');
1031
}
1188
}
1032
function attentionList(a){
1189
function attentionList(a){
1033
  window._att = a;
1190
  window._att = a;
Line 1312... Line 1469...
1312
    '<li><b>Coverage and the manager view</b> follow the existing L1&ndash;L5 sales hierarchy, so each manager sees only their team.</li>'+
1469
    '<li><b>Coverage and the manager view</b> follow the existing L1&ndash;L5 sales hierarchy, so each manager sees only their team.</li>'+
1313
    '</ul></div>';
1470
    '</ul></div>';
1314
}
1471
}
1315
 
1472
 
1316
/* ============================ EXEC DRILLDOWN ============================ */
1473
/* ============================ EXEC DRILLDOWN ============================ */
-
 
1474
// Back button shown when this journey detail was opened from another popup (a flag
-
 
1475
// drill-down or the per-exec flag list). Returns to that popup. `back` is a string
-
 
1476
// descriptor: 'flag:<code>' or 'execflags'.
-
 
1477
function execBackBtn(){
-
 
1478
  return (window._exec && window._exec.back) ? '<button class="bkbtn" onclick="execBack()">&#8592; Back</button>' : '';
-
 
1479
}
-
 
1480
function execBack(){
-
 
1481
  var ex=window._exec; if(!ex || !ex.back) return;
-
 
1482
  if(ex.back.indexOf('flag:')===0){ openFlag(ex.back.slice(5)); }
-
 
1483
  else if(ex.back==='execflags'){ openExecFlags(ex.userId, ex.name); }
-
 
1484
}
1317
function openExec(userId, name, date, tab){
1485
function openExec(userId, name, date, tab, back){
1318
  if(!userId){ return; }
1486
  if(!userId){ return; }
1319
  window._exec = {userId:userId, name:name, date:date, detail:null, tab:(tab||'timeline')};
1487
  window._exec = {userId:userId, name:name, date:date, detail:null, tab:(tab||'timeline'), back:(back||null)};
1320
  var mb=el('bj-modal-back'), m=el('bj-modal');
1488
  var mb=el('bj-modal-back'), m=el('bj-modal');
1321
  m.innerHTML='<button class="x" onclick="closeModal()">&times;</button><h3>'+esc(name)+'</h3><p class="desc">Journey detail &middot; '+esc(date)+'</p><div class="loading"><div class="spinner"></div>Loading&hellip;</div>';
1489
  m.innerHTML=execBackBtn()+'<button class="x" onclick="closeModal()">&times;</button><h3>'+esc(name)+'</h3><p class="desc">Journey detail &middot; '+esc(date)+'</p><div class="loading"><div class="spinner"></div>Loading&hellip;</div>';
1322
  mb.classList.add('show');
1490
  mb.classList.add('show');
1323
  fetch(CTX+'/beat-journey/journey-detail?userId='+userId+'&date='+encodeURIComponent(date),{headers:{'Accept':'application/json'}})
1491
  fetch(CTX+'/beat-journey/journey-detail?userId='+userId+'&date='+encodeURIComponent(date),{headers:{'Accept':'application/json'}})
1324
    .then(function(r){ return r.json(); }).then(function(d){
1492
    .then(function(r){ return r.json(); }).then(function(d){
1325
      window._mediaUrl = d.mediaUrl || '';
1493
      window._mediaUrl = d.mediaUrl || '';
1326
      window._exec.detail = d;
1494
      window._exec.detail = d;
Line 1328... Line 1496...
1328
    }).catch(function(){ el('bj-modal').innerHTML='<button class="x" onclick="closeModal()">&times;</button><p class="desc">Could not load detail.</p>'; });
1496
    }).catch(function(){ el('bj-modal').innerHTML='<button class="x" onclick="closeModal()">&times;</button><p class="desc">Could not load detail.</p>'; });
1329
}
1497
}
1330
function renderExec(tab){
1498
function renderExec(tab){
1331
  var ex=window._exec; if(!ex) return;
1499
  var ex=window._exec; if(!ex) return;
1332
  var d=ex.detail||{};
1500
  var d=ex.detail||{};
1333
  var head='<button class="x" onclick="closeModal()">&times;</button><h3>'+esc(ex.name)+'</h3>'+
1501
  var head=execBackBtn()+'<button class="x" onclick="closeModal()">&times;</button><h3>'+esc(ex.name)+'</h3>'+
1334
    '<p class="desc">Journey detail &middot; '+esc(ex.date)+' &middot; battery '+(d.batteryLevel==null?'-':d.batteryLevel+'%')+' &middot; loc-off '+(d.locationToggleCount||0)+'</p>'+
1502
    '<p class="desc">Journey detail &middot; '+esc(ex.date)+' &middot; battery '+(d.batteryLevel==null?'-':d.batteryLevel+'%')+' &middot; loc-off '+(d.locationToggleCount||0)+'</p>'+
1335
    '<div class="exectabs">'+
1503
    '<div class="exectabs">'+
1336
      '<button class="exectab'+(tab==='timeline'?' active':'')+'" onclick="renderExec(\'timeline\')">&#128221; Timeline</button>'+
1504
      '<button class="exectab'+(tab==='timeline'?' active':'')+'" onclick="renderExec(\'timeline\')">&#128221; Timeline</button>'+
1337
      '<button class="exectab'+(tab==='map'?' active':'')+'" onclick="renderExec(\'map\')">&#128205; Live Location</button>'+
1505
      '<button class="exectab'+(tab==='map'?' active':'')+'" onclick="renderExec(\'map\')">&#128205; Live Location</button>'+
1338
    '</div>';
1506
    '</div>';
Line 1817... Line 1985...
1817
  return out ? '<div class="tlags">'+out+'</div>' : '';
1985
  return out ? '<div class="tlags">'+out+'</div>' : '';
1818
}
1986
}
1819
function tlRow(x, side){
1987
function tlRow(x, side){
1820
  var s=tlStyle(x);
1988
  var s=tlStyle(x);
1821
  var tt=(x.taskType||'').toLowerCase();
1989
  var tt=(x.taskType||'').toLowerCase();
-
 
1990
  var mt=(x.markType||'').toUpperCase();
-
 
1991
  var isPartner = tt==='franchisee-visit';
-
 
1992
  var visited = mt.indexOf('CHECKOUT')>=0;
-
 
1993
  var overDwell = !x._punch && visited && toSec(x.timeSpent) > 90*60;   // #5 over 1h30m at a store
1822
  var timeTop = x.checkInTime ? hhmm(x.checkInTime) : '-';
1994
  var timeTop = x.checkInTime ? hhmm(x.checkInTime) : '-';
1823
  var tlabel = x._punch ? (x._punch==='in'?'punch in':'punch out') : (x.checkInTime?'check-in':'pending');
1995
  var tlabel = x._punch ? (x._punch==='in'?'punch in':'punch out') : (x.checkInTime?'check-in':'pending');
1824
  var meta;
1996
  var meta;
1825
  if(x._punch){
1997
  if(x._punch){
1826
    meta = (x._punch==='in'?'Punch in':'Punch out') + ' &middot; ' + hhmm(x.checkInTime);
1998
    meta = (x._punch==='in'?'Punch in':'Punch out') + ' &middot; ' + hhmm(x.checkInTime);
Line 1834... Line 2006...
1834
  var extra='';
2006
  var extra='';
1835
  if(!x._punch){
2007
  if(!x._punch){
1836
    var chips='';
2008
    var chips='';
1837
    if(x.battery!=null) chips += '<span class="tlchip">&#128267; '+x.battery+'%</span>';
2009
    if(x.battery!=null) chips += '<span class="tlchip">&#128267; '+x.battery+'%</span>';
1838
    if(x.locOff!=null) chips += '<span class="tlchip">&#128244; '+x.locOff+'</span>';
2010
    if(x.locOff!=null) chips += '<span class="tlchip">&#128244; '+x.locOff+'</span>';
-
 
2011
    // over 1h30m at this store — the #5 signal called out on the stop itself.
-
 
2012
    if(overDwell) chips += '<span class="tlbadge dwell" title="Over 1h30m at a single store">&#9201; '+esc(x.timeSpent)+'</span>';
1839
    // orders placed that day (any agenda): one badge -> drawer lists them all.
2013
    // orders placed that day (any agenda): one badge -> drawer lists them all.
-
 
2014
    // A visited partner with no order gets a "No billing" badge; a scheduled remark
1840
    // No order + billing agenda -> a parsed "scheduled" chip instead.
2015
    // still adds its parsed date chip.
1841
    if(x.orders && x.orders.length){
2016
    if(x.orders && x.orders.length){
1842
      var n=x.orders.length;
2017
      var n=x.orders.length;
1843
      chips += '<button type="button" class="tlbadge ord" title="View orders placed this day"'
2018
      chips += '<button type="button" class="tlbadge ord" title="View orders placed this day"'
1844
             + ' onclick="bjOpenOrderList('+x.taskId+')">&#129534; '+n+' order'+(n>1?'s':'')+'</button>';
2019
             + ' onclick="bjOpenOrderList('+x.taskId+')">&#129534; '+n+' order'+(n>1?'s':'')+'</button>';
1845
    } else if(x.billingAgenda){
2020
    } else {
1846
      var sch = bjScheduledFromRemark(x.remarks);
2021
      if(isPartner && visited) chips += '<span class="tlbadge nobill" title="No order placed on this visit">&#8709; No billing</span>';
1847
      if(sch) chips += '<span class="tlbadge sch" title="Scheduled (from remark)">&#128197; '+esc(sch)+'</span>';
2022
      if(x.billingAgenda){ var sch = bjScheduledFromRemark(x.remarks); if(sch) chips += '<span class="tlbadge sch" title="Scheduled (from remark)">&#128197; '+esc(sch)+'</span>'; }
1848
    }
2023
    }
1849
    var thumb=tlThumb(x.attachment);
2024
    var thumb=tlThumb(x.attachment);
1850
    if(thumb || chips) extra = '<div class="tlextra">'+thumb+chips+'</div>';
2025
    if(thumb || chips) extra = '<div class="tlextra">'+thumb+chips+'</div>';
1851
  }
2026
  }
1852
  var card='<div class="tlcard">'+
2027
  var card='<div class="tlcard'+(overDwell?' long-dwell':'')+'">'+
1853
        '<div class="tm" style="background:'+s.c+'">'+timeTop+'<small>'+tlabel+'</small></div>'+
2028
        '<div class="tm" style="background:'+s.c+'">'+timeTop+'<small>'+tlabel+'</small></div>'+
1854
        '<div class="bd"><div class="ti">'+esc(tlTitle(x))+'</div>'+
2029
        '<div class="bd"><div class="ti">'+esc(tlTitle(x))+'</div>'+
1855
          '<span class="stt" style="background:'+s.c+'">'+s.l+'</span>'+
2030
          '<span class="stt" style="background:'+s.c+'">'+s.l+'</span>'+
1856
          (tt==='lead' ? '<span class="stt unplanned">Unplanned</span>' : '')+
2031
          (tt==='lead' ? '<span class="stt unplanned">Unplanned</span>' : '')+
1857
          tlAgendas(x)+
2032
          tlAgendas(x)+
Line 1939... Line 2114...
1939
  more.style.display = hasMore ? 'block' : 'none';
2114
  more.style.display = hasMore ? 'block' : 'none';
1940
}
2115
}
1941
 
2116
 
1942
/* ===================== ORDERS DRAWER ===================== */
2117
/* ===================== ORDERS DRAWER ===================== */
1943
// All retail orders a partner placed on the visit day, each with its line items.
2118
// All retail orders a partner placed on the visit day, each with its line items.
1944
function bjOpenOrderList(fofoId){
2119
function bjOpenOrderList(fofoId, date){
1945
  if(!fofoId) return;
2120
  if(!fofoId) return;
1946
  var date=(window._exec && window._exec.date) || '';
2121
  date = date || (window._exec && window._exec.date) || window._boardDate || '';
1947
  window._od = { fofoId:fofoId, date:date };
2122
  window._od = { fofoId:fofoId, date:date };
1948
  el('bj-od-title').innerHTML='&#129534; Orders';
2123
  el('bj-od-title').innerHTML='&#129534; Orders';
1949
  el('bj-od-sub').textContent='Orders placed on '+date;
2124
  el('bj-od-sub').textContent='Orders placed on '+date;
1950
  el('bj-od-body').innerHTML='<div class="odloading"><div class="spinner"></div>Loading&hellip;</div>';
2125
  el('bj-od-body').innerHTML='<div class="odloading"><div class="spinner"></div>Loading&hellip;</div>';
1951
  el('bj-od-back').classList.add('show');
2126
  el('bj-od-back').classList.add('show');