| Line 964... |
Line 964... |
| 964 |
'<td>'+flagStatusChip(r.status)+(hits>0?' <span class="chev">▸</span>':'')+'</td>'+
|
964 |
'<td>'+flagStatusChip(r.status)+(hits>0?' <span class="chev">▸</span>':'')+'</td>'+
|
| 965 |
'</tr>';
|
965 |
'</tr>';
|
| 966 |
});
|
966 |
});
|
| 967 |
return h+'</tbody></table></div>';
|
967 |
return h+'</tbody></table></div>';
|
| 968 |
}
|
968 |
}
|
| - |
|
969 |
// Minutes-of-day from the first HH:MM in a flag detail string (-1 if none) — for sorting.
|
| - |
|
970 |
function flagTimeVal(detail){
|
| - |
|
971 |
var m=String(detail||'').match(/(\d{1,2}):(\d{2})/);
|
| - |
|
972 |
return m ? (parseInt(m[1],10)*60+parseInt(m[2],10)) : -1;
|
| - |
|
973 |
}
|
| 969 |
function openFlag(code){
|
974 |
function openFlag(code){
|
| 970 |
var list=window._visitFlags||[], r=null;
|
975 |
var list=window._visitFlags||[], r=null;
|
| 971 |
for(var i=0;i<list.length;i++){ if(list[i].code===code){ r=list[i]; break; } }
|
976 |
for(var i=0;i<list.length;i++){ if(list[i].code===code){ r=list[i]; break; } }
|
| 972 |
if(!r) return;
|
977 |
if(!r) return;
|
| 973 |
var items=r.items||[], date=window._boardDate||'';
|
978 |
var items=r.items||[], date=window._boardDate||'';
|
| - |
|
979 |
// Time-based rules (first check-in / last checkout) read best with the latest time on top.
|
| - |
|
980 |
if(code==='r7a' || code==='r7b'){
|
| - |
|
981 |
items=items.slice().sort(function(a,b){ return flagTimeVal(b.detail)-flagTimeVal(a.detail); });
|
| - |
|
982 |
}
|
| 974 |
var h='<button class="x" onclick="closeModal()">×</button><h3>'+esc(r.num)+' · '+esc(r.title)+'</h3>'+
|
983 |
var h='<button class="x" onclick="closeModal()">×</button><h3>'+esc(r.num)+' · '+esc(r.title)+'</h3>'+
|
| 975 |
'<p class="desc">'+items.length+' flagged '+(items.length===1?'visit':'visits')+
|
984 |
'<p class="desc">'+items.length+' flagged '+(items.length===1?'visit':'visits')+
|
| 976 |
(r.denom?' of '+num(r.denom)+' · '+pct(r.count,r.denom)+'%':'')+' · '+flagStatusChip(r.status)+'</p>';
|
985 |
(r.denom?' of '+num(r.denom)+' · '+pct(r.count,r.denom)+'%':'')+' · '+flagStatusChip(r.status)+'</p>';
|
| 977 |
if(!items.length){ h+='<p class="desc">Nothing flagged today.</p>'; }
|
986 |
if(!items.length){ h+='<p class="desc">Nothing flagged today.</p>'; }
|
| 978 |
else{
|
987 |
else{
|
| Line 986... |
Line 995... |
| 986 |
h+='</tbody></table>';
|
995 |
h+='</tbody></table>';
|
| 987 |
}
|
996 |
}
|
| 988 |
el('bj-modal').innerHTML=h; el('bj-modal-back').classList.add('show');
|
997 |
el('bj-modal').innerHTML=h; el('bj-modal-back').classList.add('show');
|
| 989 |
}
|
998 |
}
|
| 990 |
// Small red flag badge for the board's executive cell (count of rules tripped today).
|
999 |
// Small red flag badge for the board's executive cell (count of rules tripped today).
|
| - |
|
1000 |
// Clickable: opens a per-executive breakdown of which rules they tripped.
|
| 991 |
function flagBadge(r){
|
1001 |
function flagBadge(r){
|
| 992 |
var f=r.flags||[]; if(!f.length) return '';
|
1002 |
var f=r.flags||[]; if(!f.length) return '';
|
| - |
|
1003 |
return ' <span onclick="event.stopPropagation();openExecFlags('+r.userId+',\''+js(r.name)+'\')"'
|
| - |
|
1004 |
+' title="'+f.length+' visit-quality flag'+(f.length>1?'s':'')+' — click for detail"'
|
| 993 |
return ' <span title="'+f.length+' visit-quality flag'+(f.length>1?'s':'')+'" style="display:inline-block;font-size:10px;font-weight:700;color:#e03131;background:#fff0f0;border:1px solid #ffc9c9;border-radius:999px;padding:0 6px;vertical-align:middle">⚑ '+f.length+'</span>';
|
1005 |
+' style="cursor:pointer;display:inline-block;font-size:10px;font-weight:700;color:#e03131;background:#fff0f0;border:1px solid #ffc9c9;border-radius:999px;padding:0 6px;vertical-align:middle">⚑ '+f.length+'</span>';
|
| - |
|
1006 |
}
|
| - |
|
1007 |
// Per-executive flag drill-down: which visit-quality rules this exec tripped today, with the detail.
|
| - |
|
1008 |
function openExecFlags(userId, name){
|
| - |
|
1009 |
var row=(window._board||[]).find(function(x){ return x.userId===userId; });
|
| - |
|
1010 |
var codes=(row&&row.flags)||[];
|
| - |
|
1011 |
var byCode={}; (window._visitFlags||[]).forEach(function(f){ byCode[f.code]=f; });
|
| - |
|
1012 |
var date=window._boardDate||'';
|
| - |
|
1013 |
var h='<button class="x" onclick="closeModal()">×</button><h3>'+esc(name)+'</h3>'+
|
| - |
|
1014 |
'<p class="desc">'+codes.length+' visit-quality flag'+(codes.length===1?'':'s')+' today</p>';
|
| - |
|
1015 |
if(!codes.length){ h+='<p class="desc">No flags.</p>'; }
|
| - |
|
1016 |
else{
|
| - |
|
1017 |
h+='<table><tbody>';
|
| - |
|
1018 |
codes.forEach(function(code){
|
| - |
|
1019 |
var f=byCode[code]; if(!f) return;
|
| - |
|
1020 |
var items=f.items||[];
|
| - |
|
1021 |
var item=items.filter(function(it){ return it.userId===userId; })[0] || items.filter(function(it){ return it.name===name; })[0];
|
| - |
|
1022 |
h+='<tr><td style="font-weight:600;white-space:nowrap">'+esc(f.num)+' · '+esc(f.title)+'</td>'+
|
| - |
|
1023 |
'<td style="color:#69748c">'+esc(item?item.detail:'')+'</td></tr>';
|
| - |
|
1024 |
});
|
| - |
|
1025 |
h+='</tbody></table>';
|
| - |
|
1026 |
}
|
| - |
|
1027 |
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 →</a></p>';
|
| - |
|
1029 |
}
|
| - |
|
1030 |
el('bj-modal').innerHTML=h; el('bj-modal-back').classList.add('show');
|
| 994 |
}
|
1031 |
}
|
| 995 |
function attentionList(a){
|
1032 |
function attentionList(a){
|
| 996 |
window._att = a;
|
1033 |
window._att = a;
|
| 997 |
var order=['notPunchedIn','idleStale','geofence','locationOff','deferredAwaiting'];
|
1034 |
var order=['notPunchedIn','idleStale','geofence','locationOff','deferredAwaiting'];
|
| 998 |
var h='<div class="attrow">';
|
1035 |
var h='<div class="attrow">';
|