Subversion Repositories SmartDukaan

Rev

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

Rev 37044 Rev 37060
Line 98... Line 98...
98
  .ps-no{background:#fff0f0;color:#e03131}
98
  .ps-no{background:#fff0f0;color:#e03131}
99
  .meter{display:inline-block;width:54px;height:7px;border-radius:4px;background:#eef1f6;vertical-align:middle;margin-left:7px;overflow:hidden}
99
  .meter{display:inline-block;width:54px;height:7px;border-radius:4px;background:#eef1f6;vertical-align:middle;margin-left:7px;overflow:hidden}
100
  .meter > i{display:block;height:100%;border-radius:4px}
100
  .meter > i{display:block;height:100%;border-radius:4px}
101
  .warnflag{color:var(--orange);font-weight:700}
101
  .warnflag{color:var(--orange);font-weight:700}
102
  .badflag{color:var(--red);font-weight:700}
102
  .badflag{color:var(--red);font-weight:700}
-
 
103
  .leavebtn{border:1px solid #ffd8a8;background:#fff4e6;color:#e8590c;font-weight:650;font-size:11px;padding:2px 9px;border-radius:6px;cursor:pointer;white-space:nowrap}
-
 
104
  .leavebtn:hover{background:#ffe8cc}
-
 
105
  .leavebtn:disabled{opacity:.6;cursor:default}
103
  .rowwarn{background:#fff9f0}
106
  .rowwarn{background:#fff9f0}
104
  .rowbad{background:#fff5f5}
107
  .rowbad{background:#fff5f5}
105
  .bstat{display:inline-block;font-size:10.5px;font-weight:700;padding:3px 9px;border-radius:999px;white-space:nowrap}
108
  .bstat{display:inline-block;font-size:10.5px;font-weight:700;padding:3px 9px;border-radius:999px;white-space:nowrap}
106
  .bstat.ok{background:#ebfbee;color:#2f9e44}
109
  .bstat.ok{background:#ebfbee;color:#2f9e44}
107
  .bstat.warn{background:#fff4e6;color:#e8590c}
110
  .bstat.warn{background:#fff4e6;color:#e8590c}
Line 486... Line 489...
486
function leakbox(t, v, color){ return '<div class="leakbox"><div class="lt">'+t+'</div><div class="lv"'+(color?' style="color:'+color+'"':'')+'>'+num(v)+'</div></div>'; }
489
function leakbox(t, v, color){ return '<div class="leakbox"><div class="lt">'+t+'</div><div class="lv"'+(color?' style="color:'+color+'"':'')+'>'+num(v)+'</div></div>'; }
487
 
490
 
488
function renderToday(d){
491
function renderToday(d){
489
  var k=d.kpis, f=d.funnel, s=d.stops, a=d.attention;
492
  var k=d.kpis, f=d.funnel, s=d.stops, a=d.attention;
490
  window._canApprove = !!d.canApprove;
493
  window._canApprove = !!d.canApprove;
-
 
494
  window._canMarkLeave = !!d.canMarkLeave;
491
  window._mediaUrl = d.mediaUrl || window._mediaUrl || '';
495
  window._mediaUrl = d.mediaUrl || window._mediaUrl || '';
492
  window._board = d.board || [];          // funnel drill-down filters this
496
  window._board = d.board || [];          // funnel drill-down filters this
493
  window._boardDate = d.date || '';
497
  window._boardDate = d.date || '';
494
  window._leadsList = d.leadsList || [];  // leads drill-down
498
  window._leadsList = d.leadsList || [];  // leads drill-down
495
  window._execList = d.execList || [];    // main-card drill-downs
499
  window._execList = d.execList || [];    // main-card drill-downs
Line 667... Line 671...
667
var CARD_META = {
671
var CARD_META = {
668
  total:       {title:'Total Executives', desc:'Everyone in your team',        f:function(r){ return true; }},
672
  total:       {title:'Total Executives', desc:'Everyone in your team',        f:function(r){ return true; }},
669
  scheduled:   {title:'Scheduled PJP',     desc:'Executives with a beat today', f:function(r){ return r.hasBeat; }},
673
  scheduled:   {title:'Scheduled PJP',     desc:'Executives with a beat today', f:function(r){ return r.hasBeat; }},
670
  agenda:      {title:'Agenda Filled',     desc:'Beats with the agenda filled', f:function(r){ return r.agendaFilled; },
674
  agenda:      {title:'Agenda Filled',     desc:'Beats with the agenda filled', f:function(r){ return r.agendaFilled; },
671
                dropLabel:'Agenda not filled (scheduled)', drop:function(r){ return r.hasBeat && !r.agendaFilled; }},
675
                dropLabel:'Agenda not filled (scheduled)', drop:function(r){ return r.hasBeat && !r.agendaFilled; }},
672
  unscheduled: {title:'Unscheduled',       desc:'No PJP scheduled today',       f:function(r){ return !r.hasBeat; }}
676
  unscheduled: {title:'Unscheduled',       desc:'No PJP scheduled today',       f:function(r){ return !r.hasBeat && !r.onLeave; }}
673
};
677
};
674
// Renders an executive roster table (used by the card drill-downs).
678
// Renders an executive roster table (used by the card drill-downs).
675
function execRows(list, date){
679
function execRows(list, date){
676
  if(!list.length) return '<p class="desc">None.</p>';
680
  if(!list.length) return '<p class="desc">None.</p>';
677
  var h='<table><thead><tr><th>Executive</th><th>Beat</th><th>Agenda</th><th>Status</th></tr></thead><tbody>';
681
  var h='<table><thead><tr><th>Executive</th><th>Beat</th><th>Agenda</th><th>Status</th></tr></thead><tbody>';
678
  list.forEach(function(r){
682
  list.forEach(function(r){
679
    var A=r.userId+",'"+js(r.name)+"','"+date+"'";
683
    var A=r.userId+",'"+js(r.name)+"','"+date+"'";
680
    var go = r.userId ? ' class="clickable" onclick="openExec('+A+')"' : '';
684
    var go = r.userId ? ' class="clickable" onclick="openExec('+A+')"' : '';
681
    var agenda = !r.hasBeat ? '<span style="color:#97a1b5">&mdash;</span>'
685
    var agenda = !r.hasBeat ? '<span style="color:#97a1b5">&mdash;</span>'
682
      : (r.agendaFilled ? '<span style="color:#2f9e44">&#10003;</span>' : '<span class="badflag">&#10007;</span>');
686
      : (r.agendaFilled ? '<span style="color:#2f9e44">&#10003;</span>' : '<span class="badflag">&#10007;</span>');
-
 
687
    // Unscheduled execs can be marked on leave (manager / super-user only; server-enforced).
-
 
688
    var leaveBtn = (window._canMarkLeave && r.authUserId && !r.hasBeat && !r.onLeave)
-
 
689
      ? ' <button class="leavebtn" onclick="event.stopPropagation();markLeave('+r.authUserId+",'"+js(r.name)+"',this)\">Mark leave</button>" : '';
683
    h+='<tr'+go+'>'+
690
    h+='<tr'+go+'>'+
684
       '<td><b>'+esc(r.name)+'</b></td>'+
691
       '<td><b>'+esc(r.name)+'</b></td>'+
685
       '<td>'+esc(r.beatName||'-')+'</td>'+
692
       '<td>'+esc(r.beatName||'-')+'</td>'+
686
       '<td style="text-align:center">'+agenda+'</td>'+
693
       '<td style="text-align:center">'+agenda+'</td>'+
687
       '<td>'+esc(r.statusLabel||'')+'</td></tr>';
694
       '<td>'+esc(r.statusLabel||'')+leaveBtn+'</td></tr>';
688
  });
695
  });
689
  return h+'</tbody></table>';
696
  return h+'</tbody></table>';
690
}
697
}
691
function openCards(key){
698
function openCards(key){
692
  var m=CARD_META[key]; if(!m) return;
699
  var m=CARD_META[key]; if(!m) return;
Line 944... Line 951...
944
      else closeModal();
951
      else closeModal();
945
    })
952
    })
946
    .catch(function(){ btn.disabled=false; btn.textContent='Approve'; alert('Approve failed (not authorised, or server error).'); });
953
    .catch(function(){ btn.disabled=false; btn.textContent='Approve'; alert('Approve failed (not authorised, or server error).'); });
947
}
954
}
948
 
955
 
-
 
956
// Mark an unscheduled executive on leave for the board date. Manager / super-user only
-
 
957
// (server-enforced). Optimistically drops them from the Unscheduled list on success.
-
 
958
function markLeave(authUserId, name, btn){
-
 
959
  if(!authUserId) return;
-
 
960
  var reason = prompt('Mark '+name+' on leave for '+(window._boardDate||'today')+'?\nOptional reason:', '');
-
 
961
  if(reason===null) return;   // cancelled
-
 
962
  btn.disabled=true; btn.textContent='Marking…';
-
 
963
  var body='authUserId='+authUserId+'&date='+encodeURIComponent(window._boardDate||'')+'&reason='+encodeURIComponent(reason||'');
-
 
964
  fetch(CTX+'/beat-journey/mark-leave',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:body})
-
 
965
    .then(function(r){ if(!r.ok) throw new Error('HTTP '+r.status); return r.json(); })
-
 
966
    .then(function(){
-
 
967
      (window._execList||[]).forEach(function(x){ if(x.authUserId===authUserId){ x.onLeave=true; x.statusLabel='On leave'; } });
-
 
968
      openCards('unscheduled');   // re-render (row now filtered out of Unscheduled)
-
 
969
      loadToday();                // refresh KPI tiles / counts in the background
-
 
970
    })
-
 
971
    .catch(function(){ btn.disabled=false; btn.textContent='Mark leave'; alert('Could not mark leave (not authorised, or server error).'); });
-
 
972
}
-
 
973
 
949
/* ============================ PERIOD ============================ */
974
/* ============================ PERIOD ============================ */
950
function loadPeriod(){
975
function loadPeriod(){
951
  spinner();
976
  spinner();
952
  var u = BASE + '/data?startDate=' + encodeURIComponent(el('bj-start').value) + '&endDate=' + encodeURIComponent(el('bj-end').value) + qs();
977
  var u = BASE + '/data?startDate=' + encodeURIComponent(el('bj-start').value) + '&endDate=' + encodeURIComponent(el('bj-end').value) + qs();
953
  fetch(u, {headers:{'Accept':'application/json'}})
978
  fetch(u, {headers:{'Accept':'application/json'}})