Worker cards now handle sparse-to-rich data gracefully: - Name only? Shows name + 'New — data builds with placements' - Name + role? Shows name + role tag - Name + role + skills + certs? Shows full tag row - Has reliability data? Shows colored meter bars - No metrics? No empty bars, no 0% — just what's there Contract cards: urgency dot, progress bar, fill count. Workers inside: avatar initials, name, role, location, skill/cert tags (blue/green), archetype (purple), reliability/availability bars — all ONLY when data exists. GitHub-style dark theme. Call/SMS per worker. Search collapsed. ADR-021 compliant: works with a name and earns everything else. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
176 lines
13 KiB
HTML
176 lines
13 KiB
HTML
<!DOCTYPE html>
|
|
<html><head>
|
|
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>Staffing Co-Pilot</title>
|
|
<style>
|
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
body{font-family:-apple-system,system-ui,sans-serif;background:#0b0f19;color:#c9d1d9;font-size:13px}
|
|
.bar{background:#161b22;padding:10px 20px;border-bottom:1px solid #21262d;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px}
|
|
.bar h1{font-size:15px;font-weight:600;color:#f0f6fc}.bar .right{font-size:11px;color:#8b949e}
|
|
.pipeline{display:flex;gap:1px;margin:16px 16px 0;background:#161b22;border-radius:10px;overflow:hidden;border:1px solid #21262d}
|
|
.pip{flex:1;padding:14px 8px;text-align:center}.pip .n{font-size:28px;font-weight:800}.pip .l{font-size:9px;text-transform:uppercase;letter-spacing:1px;color:#8b949e;margin-top:2px}
|
|
.pip.red .n{color:#f85149}.pip.yel .n{color:#d29922}.pip.blu .n{color:#58a6ff}.pip.grn .n{color:#3fb950}
|
|
.content{padding:16px;max-width:1100px;margin:0 auto}
|
|
.sl{font-size:11px;color:#8b949e;text-transform:uppercase;letter-spacing:1px;font-weight:600;margin:16px 0 8px}
|
|
.contract{background:#161b22;border:1px solid #21262d;border-radius:10px;margin-bottom:12px;overflow:hidden}
|
|
.chdr{padding:12px 16px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid #21262d;flex-wrap:wrap;gap:8px}
|
|
.dot{width:10px;height:10px;border-radius:50%;flex-shrink:0;display:inline-block;margin-right:6px}
|
|
.dot.urgent{background:#f85149}.dot.high{background:#d29922}.dot.medium{background:#58a6ff}.dot.filled{background:#3fb950}
|
|
.client{font-size:14px;font-weight:600;color:#f0f6fc}.need{font-size:11px;color:#8b949e;margin-left:8px}
|
|
.prog{background:#21262d;border-radius:10px;height:6px;width:70px;overflow:hidden;display:inline-block;margin-right:4px}
|
|
.prog .fill{height:100%;border-radius:10px}.fill.full{background:#3fb950}.fill.part{background:#d29922}
|
|
.wk{display:flex;gap:10px;padding:10px 14px;border-bottom:1px solid #111820;align-items:flex-start}
|
|
.wk:last-child{border:none}.wk:hover{background:#1c2333}
|
|
.av{width:34px;height:34px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:13px;color:#f0f6fc;flex-shrink:0;background:#1a2744}
|
|
.wk .bd{flex:1;min-width:0}
|
|
.nm{font-weight:600;color:#f0f6fc;font-size:13px}.loc{color:#8b949e;font-size:11px;margin-left:6px}
|
|
.nt{color:#8b949e;font-size:11px;font-style:italic;margin-top:2px}
|
|
.tags{display:flex;gap:3px;flex-wrap:wrap;margin-top:4px}
|
|
.tg{padding:1px 6px;border-radius:8px;font-size:9px;font-weight:500}
|
|
.tg.s{background:#1a2744;color:#58a6ff;border:1px solid #1f3d68}
|
|
.tg.c{background:#1a3a2a;color:#3fb950;border:1px solid #238636}
|
|
.tg.a{background:#2a1a3a;color:#bc8cff;border:1px solid #553098}
|
|
.meters{display:flex;gap:10px;margin-top:5px}
|
|
.mt{display:flex;align-items:center;gap:3px}.mt .lb{color:#8b949e;font-size:9px}
|
|
.mt .br{width:40px;height:3px;background:#21262d;border-radius:2px;overflow:hidden}
|
|
.mt .vl{height:100%;border-radius:2px}.vl.hi{background:#3fb950}.vl.md{background:#d29922}.vl.lo{background:#f85149}
|
|
.mt .pc{font-size:9px;font-weight:600}
|
|
.acts{display:flex;gap:3px;flex-shrink:0;align-self:center}
|
|
.wb{padding:4px 8px;border-radius:4px;font-size:10px;cursor:pointer;border:1px solid #21262d;background:#161b22;color:#8b949e}
|
|
.wb:hover{border-color:#58a6ff;color:#58a6ff}.wb.pr{background:#1f3d68;color:#58a6ff;border-color:#1f3d68}
|
|
.sp{font-size:10px;color:#484f58;margin-top:3px}
|
|
.sa{margin-top:16px;background:#161b22;border:1px solid #21262d;border-radius:10px;overflow:hidden}
|
|
.sa summary{cursor:pointer;color:#8b949e;font-size:12px;list-style:none;padding:12px 14px}
|
|
.sa summary::-webkit-details-marker{display:none}
|
|
.sa .inner{padding:0 14px 14px}
|
|
.sa input[type=text]{width:100%;padding:10px;background:#0b0f19;border:1px solid #21262d;border-radius:6px;color:#f0f6fc;font-size:13px;outline:none;margin-bottom:8px}
|
|
.srow{display:flex;gap:6px;margin-bottom:8px}.sa select{flex:1;padding:6px;background:#0b0f19;border:1px solid #21262d;border-radius:4px;color:#c9d1d9;font-size:11px}
|
|
.sbtn{width:100%;padding:8px;background:#7c3aed;border:none;border-radius:6px;color:#fff;font-size:12px;font-weight:600;cursor:pointer}
|
|
.ft{text-align:center;padding:16px;color:#484f58;font-size:11px}.ft a{color:#58a6ff;text-decoration:none}
|
|
.ld{color:#484f58;text-align:center;padding:16px}
|
|
@media(max-width:768px){.pipeline{flex-wrap:wrap}.pip{min-width:25%}.wk{flex-direction:column}.chdr{flex-direction:column;align-items:flex-start}}
|
|
</style></head><body>
|
|
<div class="bar"><h1>Staffing Co-Pilot</h1><div class="right" id="status">Loading...</div></div>
|
|
<div class="pipeline" id="pipeline"></div>
|
|
<div class="content">
|
|
<div class="sl">Your Contracts</div>
|
|
<div id="contracts"><div class="ld">Loading...</div></div>
|
|
<details class="sa"><summary>Search workers</summary><div class="inner">
|
|
<input type="text" id="sq" placeholder="Type what you need..." onkeydown="if(event.key==='Enter')doSearch()">
|
|
<div class="srow"><select id="sst"><option value="">Any State</option><option>IL</option><option>IN</option><option>OH</option><option>MO</option></select>
|
|
<select id="srl"><option value="">Any Role</option><option>Forklift Operator</option><option>Machine Operator</option><option>Welder</option><option>Loader</option></select></div>
|
|
<button class="sbtn" onclick="doSearch()">Search</button><div id="sresults"></div></div></details>
|
|
<div class="ft"><a href="proof">Proof of Work</a></div>
|
|
</div>
|
|
<script>
|
|
var P=location.pathname.indexOf('/lakehouse')>=0?'/lakehouse':'';var A=location.origin+P;
|
|
var AC=['#1a2744','#1a3a2a','#2a1a3a','#3a2a1a','#1a3a3a'];
|
|
window.addEventListener('load',loadDay);
|
|
function loadDay(){
|
|
fetch(A+'/simulation/run',{method:'POST',headers:{'Content-Type':'application/json'}})
|
|
.then(function(r){return r.json()}).then(function(d){
|
|
var t=d.days?d.days[0]:null,s=d.summary||{};
|
|
renderPip(t);renderCon(t);
|
|
document.getElementById('status').textContent=s.total_filled+'/'+s.total_needed+' filled';
|
|
}).catch(function(e){document.getElementById('contracts').textContent='Error: '+e.message})}
|
|
|
|
function renderPip(t){
|
|
var el=document.getElementById('pipeline');el.textContent='';
|
|
var u=0,f=0,fl=0,n=0;
|
|
if(t&&t.contracts)t.contracts.forEach(function(c){n++;if(c.priority==='urgent')u++;if(c.filled>=c.headcount)fl++;else f++});
|
|
[['red',u,'Urgent'],['yel',f,'Filling'],['blu',n,'Total'],['grn',fl,'Filled']].forEach(function(p){
|
|
var d=document.createElement('div');d.className='pip '+p[0];
|
|
var nn=document.createElement('div');nn.className='n';nn.textContent=p[1];
|
|
var l=document.createElement('div');l.className='l';l.textContent=p[2];
|
|
d.appendChild(nn);d.appendChild(l);el.appendChild(d)})}
|
|
|
|
function pw(text){
|
|
var p=(text||'').split(/\u2014|\u2013|—/),nm=p[0]?p[0].trim():'',rest=p[1]?p[1].trim():'';
|
|
var rm=rest.match(/^(.+?) in (.+?)\./),sm=rest.match(/Skills: ([^.]+)/),cm=rest.match(/Certs?: ([^.]+)/);
|
|
var rr=rest.match(/Reliability: ([\d.]+)/),av=rest.match(/Availability: ([\d.]+)/),ar=rest.match(/Archetype: (\w+)/);
|
|
return{nm:nm,role:rm?rm[1]:'',loc:rm?rm[2]:'',
|
|
skills:sm?sm[1].split('|').slice(0,3):[],certs:cm?cm[1].split('|').filter(function(x){return x&&x!=='none'}).slice(0,2):[],
|
|
rel:rr?parseFloat(rr[1]):0,avail:av?parseFloat(av[1]):0,arch:ar?ar[1]:'',hasM:!!rr}}
|
|
|
|
function renderCon(today){
|
|
var el=document.getElementById('contracts');el.textContent='';
|
|
if(!today||!today.contracts)return;
|
|
today.contracts.sort(function(a,b){var p={urgent:0,high:1,medium:2,low:3};
|
|
if(a.filled>=a.headcount&&b.filled<b.headcount)return 1;if(a.filled<a.headcount&&b.filled>=b.headcount)return -1;
|
|
return(p[a.priority]||2)-(p[b.priority]||2)});
|
|
today.contracts.forEach(function(c){
|
|
var F=c.filled>=c.headcount,cd=document.createElement('div');cd.className='contract';
|
|
var h=document.createElement('div');h.className='chdr';
|
|
var d=document.createElement('span');d.className='dot '+(F?'filled':c.priority);
|
|
var cl=document.createElement('span');cl.className='client';cl.textContent=c.client;
|
|
var nd=document.createElement('span');nd.className='need';nd.textContent=c.role+' x'+c.headcount+' · '+(c.city||c.state)+' · '+c.start;
|
|
var pg=document.createElement('div');pg.className='prog';var fl=document.createElement('div');fl.className='fill '+(F?'full':'part');
|
|
fl.style.width=Math.round(c.filled/c.headcount*100)+'%';pg.appendChild(fl);
|
|
var ct=document.createElement('span');ct.style.cssText='font-size:12px;font-weight:600;color:#f0f6fc;margin-left:4px';ct.textContent=c.filled+'/'+c.headcount;
|
|
h.appendChild(d);h.appendChild(cl);h.appendChild(nd);h.appendChild(pg);h.appendChild(ct);cd.appendChild(h);
|
|
|
|
if(c.matches&&c.matches.length){var wd=document.createElement('div');
|
|
c.matches.slice(0,c.headcount).forEach(function(m,i){
|
|
var w=pw(m.chunk_text||'');if(!w.nm)w.nm=m.name||m.doc_id;
|
|
var wk=document.createElement('div');wk.className='wk';
|
|
var av=document.createElement('div');av.className='av';av.style.background=AC[i%AC.length];
|
|
av.textContent=(w.nm||'?').split(' ').map(function(n){return(n[0]||'').toUpperCase()}).join('').substring(0,2);
|
|
wk.appendChild(av);
|
|
var bd=document.createElement('div');bd.className='bd';
|
|
var r1=document.createElement('div');var nm=document.createElement('span');nm.className='nm';nm.textContent=w.nm;r1.appendChild(nm);
|
|
if(w.role){var rl=document.createElement('span');rl.className='loc';rl.textContent=w.role;r1.appendChild(rl)}
|
|
if(w.loc){var lc=document.createElement('span');lc.className='loc';lc.textContent=w.loc;r1.appendChild(lc)}
|
|
bd.appendChild(r1);
|
|
if(w.skills.length||w.certs.length||w.arch){var tgs=document.createElement('div');tgs.className='tags';
|
|
w.skills.forEach(function(s){var t=document.createElement('span');t.className='tg s';t.textContent=s.trim();tgs.appendChild(t)});
|
|
w.certs.forEach(function(c){var t=document.createElement('span');t.className='tg c';t.textContent=c.trim();tgs.appendChild(t)});
|
|
if(w.arch){var t=document.createElement('span');t.className='tg a';t.textContent=w.arch;tgs.appendChild(t)}
|
|
bd.appendChild(tgs)}
|
|
if(w.hasM){var mt=document.createElement('div');mt.className='meters';
|
|
addM(mt,'Rel',w.rel);addM(mt,'Avail',w.avail);bd.appendChild(mt)}
|
|
else if(!w.skills.length&&!w.certs.length){var sp=document.createElement('div');sp.className='sp';
|
|
sp.textContent='New — data builds with placements';bd.appendChild(sp)}
|
|
wk.appendChild(bd);
|
|
var ac=document.createElement('div');ac.className='acts';
|
|
var cb=document.createElement('button');cb.className='wb pr';cb.textContent='Call';
|
|
var sb=document.createElement('button');sb.className='wb';sb.textContent='SMS';
|
|
ac.appendChild(cb);ac.appendChild(sb);wk.appendChild(ac);wd.appendChild(wk)});cd.appendChild(wd)}
|
|
el.appendChild(cd)})}
|
|
|
|
function addM(p,l,v){var m=document.createElement('div');m.className='mt';
|
|
var lb=document.createElement('span');lb.className='lb';lb.textContent=l;
|
|
var br=document.createElement('div');br.className='br';
|
|
var vl=document.createElement('div');vl.className='vl '+(v>=.8?'hi':v>=.5?'md':'lo');
|
|
vl.style.width=Math.round(v*100)+'%';br.appendChild(vl);
|
|
var pc=document.createElement('span');pc.className='pc';
|
|
pc.style.color=v>=.8?'#3fb950':v>=.5?'#d29922':'#f85149';
|
|
pc.textContent=Math.round(v*100)+'%';
|
|
m.appendChild(lb);m.appendChild(br);m.appendChild(pc);p.appendChild(m)}
|
|
|
|
function doSearch(){
|
|
var q=document.getElementById('sq').value.trim();if(!q)return;
|
|
var st=document.getElementById('sst').value,rl=document.getElementById('srl').value;
|
|
var out=document.getElementById('sresults');out.textContent='Searching...';
|
|
var f="CAST(reliability AS DOUBLE)>=0.5";
|
|
if(st)f+=" AND state='"+st+"'";if(rl)f+=" AND role='"+rl+"'";
|
|
fetch(A+'/search',{method:'POST',headers:{'Content-Type':'application/json'},
|
|
body:JSON.stringify({question:q,index_name:'workers_500k_v1',sql_filter:f,dataset:'workers_500k',id_column:'worker_id',top_k:8,generate:false})
|
|
}).then(function(r){return r.json()}).then(function(d){
|
|
out.textContent='';var src=d.sources||[];if(!src.length){out.textContent='No matches.';return}
|
|
var h=document.createElement('div');h.style.cssText='color:#8b949e;font-size:11px;margin-bottom:8px';
|
|
h.textContent=(d.sql_matches||0)+' matches, '+src.length+' best ('+(d.duration_ms||0)+'ms)';out.appendChild(h);
|
|
src.forEach(function(s,i){var w=pw(s.chunk_text);if(!w.nm)w.nm=s.doc_id;
|
|
var wk=document.createElement('div');wk.className='wk';wk.style.cssText='background:#161b22;border:1px solid #21262d;border-radius:8px;margin-bottom:6px';
|
|
var av=document.createElement('div');av.className='av';av.style.background=AC[i%AC.length];
|
|
av.textContent=(w.nm||'?').split(' ').map(function(n){return(n[0]||'').toUpperCase()}).join('').substring(0,2);wk.appendChild(av);
|
|
var bd=document.createElement('div');bd.className='bd';
|
|
var nm=document.createElement('span');nm.className='nm';nm.textContent=w.nm;bd.appendChild(nm);
|
|
if(w.role||w.loc){var rl=document.createElement('span');rl.className='loc';rl.textContent=[w.role,w.loc].filter(Boolean).join(' · ');bd.appendChild(rl)}
|
|
if(w.skills.length||w.certs.length){var tgs=document.createElement('div');tgs.className='tags';
|
|
w.skills.forEach(function(s){var t=document.createElement('span');t.className='tg s';t.textContent=s.trim();tgs.appendChild(t)});
|
|
w.certs.forEach(function(c){var t=document.createElement('span');t.className='tg c';t.textContent=c.trim();tgs.appendChild(t)});bd.appendChild(tgs)}
|
|
if(w.hasM){var mt=document.createElement('div');mt.className='meters';addM(mt,'Rel',w.rel);addM(mt,'Avail',w.avail);bd.appendChild(mt)}
|
|
wk.appendChild(bd);out.appendChild(wk)})
|
|
}).catch(function(e){out.textContent='Error: '+e.message})}
|
|
</script></body></html>
|