Graceful sparse data: show what exists, hide what doesn't
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>
This commit is contained in:
parent
13b01fee9f
commit
875cfadc3d
@ -4,331 +4,172 @@
|
||||
<title>Staffing Co-Pilot</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;background:#0b0f19;color:#c9d1d9;font-size:13px;-webkit-font-smoothing:antialiased}
|
||||
|
||||
/* Top bar */
|
||||
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 h1 span{color:#7c3aed}
|
||||
.bar .right{display:flex;gap:12px;align-items:center;font-size:11px;color:#8b949e}
|
||||
|
||||
/* Pipeline counters */
|
||||
.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;font-variant-numeric:tabular-nums}
|
||||
.pip .l{font-size:9px;text-transform:uppercase;letter-spacing:1px;color:#8b949e;margin-top:2px}
|
||||
.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 */
|
||||
.content{padding:16px;max-width:1200px;margin:0 auto}
|
||||
.section-label{font-size:11px;color:#8b949e;text-transform:uppercase;letter-spacing:1px;font-weight:600;margin:20px 0 10px;display:flex;justify-content:space-between;align-items:center}
|
||||
|
||||
/* Contract card */
|
||||
.contract{background:#161b22;border:1px solid #21262d;border-radius:10px;margin-bottom:14px;overflow:hidden}
|
||||
.contract .hdr{padding:14px 16px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid #21262d;flex-wrap:wrap;gap:8px}
|
||||
.contract .hdr .left{display:flex;align-items:center;gap:10px}
|
||||
.contract .urgency{width:10px;height:10px;border-radius:50%;flex-shrink:0}
|
||||
.urgency.urgent{background:#f85149}.urgency.high{background:#d29922}.urgency.medium{background:#58a6ff}.urgency.low{background:#3fb950}.urgency.filled{background:#3fb950}
|
||||
.contract .client{font-size:15px;font-weight:600;color:#f0f6fc}
|
||||
.contract .need{font-size:12px;color:#8b949e}
|
||||
.contract .hdr .right{display:flex;align-items:center;gap:8px}
|
||||
.progress{background:#21262d;border-radius:10px;height:6px;width:80px;overflow:hidden}
|
||||
.progress .fill{height:100%;border-radius:10px;transition:width .3s}
|
||||
.fill.full{background:#3fb950}.fill.partial{background:#d29922}.fill.empty{background:#484f58}
|
||||
.contract .hdr .count{font-size:12px;font-weight:600;color:#f0f6fc}
|
||||
|
||||
/* Worker card inside contract */
|
||||
.workers{padding:6px}
|
||||
.worker{display:flex;align-items:flex-start;gap:12px;padding:10px 12px;border-radius:8px;transition:background .15s}
|
||||
.worker:hover{background:#1c2333}
|
||||
.worker .avatar{width:36px;height:36px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px;color:#f0f6fc;flex-shrink:0}
|
||||
.avatar.a1{background:#1a3a2a}.avatar.a2{background:#1a2a3a}.avatar.a3{background:#2a1a3a}.avatar.a4{background:#3a2a1a}.avatar.a5{background:#1a3a3a}
|
||||
.worker .info{flex:1;min-width:0}
|
||||
.worker .name-row{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
|
||||
.worker .wname{font-weight:600;color:#f0f6fc;font-size:13px}
|
||||
.worker .wloc{color:#8b949e;font-size:11px}
|
||||
.worker .tags{display:flex;gap:4px;flex-wrap:wrap;margin-top:4px}
|
||||
.tag{padding:1px 7px;border-radius:10px;font-size:10px;font-weight:500}
|
||||
.tag.skill{background:#1a2744;color:#58a6ff;border:1px solid #1f3d68}
|
||||
.tag.cert{background:#1a3a2a;color:#3fb950;border:1px solid #238636}
|
||||
.tag.type{background:#2a1a3a;color:#bc8cff;border:1px solid #553098}
|
||||
.worker .metrics{display:flex;gap:12px;margin-top:6px;align-items:center}
|
||||
.metric{display:flex;align-items:center;gap:4px}
|
||||
.metric .label{color:#8b949e;font-size:10px}
|
||||
.metric .bar{width:48px;height:4px;background:#21262d;border-radius:2px;overflow:hidden}
|
||||
.metric .bar .val{height:100%;border-radius:2px}
|
||||
.bar .high{background:#3fb950}.bar .med{background:#d29922}.bar .low{background:#f85149}
|
||||
.metric .pct{font-size:10px;font-weight:600;font-variant-numeric:tabular-nums}
|
||||
.worker .actions{display:flex;gap:4px;flex-shrink:0;align-self:center}
|
||||
.wbtn{padding:4px 10px;border-radius:4px;font-size:10px;cursor:pointer;border:1px solid #21262d;background:#161b22;color:#8b949e;font-weight:500}
|
||||
.wbtn:hover{border-color:#58a6ff;color:#58a6ff}
|
||||
.wbtn.primary{background:#1f3d68;color:#58a6ff;border-color:#1f3d68}
|
||||
|
||||
/* Alerts strip */
|
||||
.alerts{display:flex;gap:8px;margin:16px 16px 0;flex-wrap:wrap}
|
||||
.alrt{padding:6px 12px;border-radius:6px;font-size:11px;display:flex;align-items:center;gap:4px}
|
||||
.alrt.w{background:#2d1b00;color:#d29922;border:1px solid #533d13}
|
||||
.alrt.i{background:#0d1d33;color:#58a6ff;border:1px solid #1f3d68}
|
||||
.alrt.g{background:#0d261a;color:#3fb950;border:1px solid #238636}
|
||||
|
||||
/* Search */
|
||||
.search-area{margin-top:20px;background:#161b22;border:1px solid #21262d;border-radius:10px;padding:14px}
|
||||
.search-area summary{cursor:pointer;color:#8b949e;font-size:12px;list-style:none;display:flex;align-items:center;gap:6px}
|
||||
.search-area summary::-webkit-details-marker{display:none}
|
||||
.search-area[open] summary{margin-bottom:10px}
|
||||
.search-area input[type=text]{width:100%;padding:10px 14px;background:#0b0f19;border:1px solid #21262d;border-radius:6px;color:#f0f6fc;font-size:13px;outline:none;margin-bottom:8px}
|
||||
.search-area input:focus{border-color:#58a6ff}
|
||||
.srow{display:flex;gap:6px;margin-bottom:8px}
|
||||
.search-area select{flex:1;padding:7px;background:#0b0f19;border:1px solid #21262d;border-radius:4px;color:#c9d1d9;font-size:11px}
|
||||
.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}
|
||||
.sbtn:hover{background:#6d28d9}
|
||||
#sresults{margin-top:10px}
|
||||
|
||||
.footer{text-align:center;padding:16px;color:#484f58;font-size:11px;margin-top:20px}
|
||||
.footer a{color:#58a6ff;text-decoration:none}
|
||||
.loading{color:#484f58;text-align:center;padding:16px}
|
||||
|
||||
@media(max-width:768px){
|
||||
.pipeline{flex-wrap:wrap}.pip{min-width:25%}
|
||||
.worker{flex-direction:column}.worker .actions{align-self:flex-start}
|
||||
.alerts{flex-direction:column}
|
||||
.contract .hdr{flex-direction:column;align-items:flex-start}
|
||||
}
|
||||
.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><span>●</span> Staffing Co-Pilot</h1>
|
||||
<div class="right"><span id="status">Loading...</span></div>
|
||||
</div>
|
||||
<div class="bar"><h1>Staffing Co-Pilot</h1><div class="right" id="status">Loading...</div></div>
|
||||
<div class="pipeline" id="pipeline"></div>
|
||||
<div class="alerts" id="alerts"></div>
|
||||
<div class="content">
|
||||
<div class="section-label"><span>Your Contracts</span><span id="day-label"></span></div>
|
||||
<div id="contracts"><div class="loading">Loading your day...</div></div>
|
||||
|
||||
<details class="search-area">
|
||||
<summary>🔍 Search all 500,000 workers</summary>
|
||||
<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><option>TN</option><option>KY</option><option>WI</option><option>MI</option></select>
|
||||
<select id="srl"><option value="">Any Role</option><option>Forklift Operator</option><option>Machine Operator</option><option>Assembler</option><option>Loader</option><option>Quality Tech</option><option>Welder</option><option>Sanitation Worker</option><option>Maintenance Tech</option></select>
|
||||
</div>
|
||||
<button class="sbtn" onclick="doSearch()">Search</button>
|
||||
<div id="sresults"></div>
|
||||
</details>
|
||||
|
||||
<div class="footer"><a href="proof">View Proof of Work</a> · 500K workers · 673K AI-indexed</div>
|
||||
<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 colors = ['a1','a2','a3','a4','a5'];
|
||||
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})}
|
||||
|
||||
window.addEventListener('load', loadDay);
|
||||
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 loadDay() {
|
||||
fetch(A + '/simulation/run', { method: 'POST', headers: {'Content-Type':'application/json'} })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
var today = d.days ? d.days[0] : null;
|
||||
var sum = d.summary || {};
|
||||
renderPipeline(today);
|
||||
renderContracts(today);
|
||||
document.getElementById('status').textContent = sum.total_filled + '/' + sum.total_needed + ' filled · ' + sum.emergencies + ' urgent';
|
||||
document.getElementById('day-label').textContent = today ? today.label + ' · ' + today.staffer : '';
|
||||
})
|
||||
.catch(function(e) { document.getElementById('contracts').textContent = 'Error: ' + e.message; });
|
||||
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}}
|
||||
|
||||
// Alerts
|
||||
fetch(A + '/sql', { method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({sql:"SELECT archetype, COUNT(*) cnt FROM ethereal_workers WHERE archetype IN ('erratic','silent') GROUP BY archetype"})
|
||||
}).then(function(r){return r.json()}).then(function(d){
|
||||
var el = document.getElementById('alerts');
|
||||
el.textContent = '';
|
||||
(d.rows||[]).forEach(function(r){
|
||||
var a=document.createElement('div');
|
||||
a.className='alrt '+(r.archetype==='erratic'?'w':'i');
|
||||
a.textContent=(r.archetype==='erratic'?'⚠ ':'📵 ')+r.cnt+' '+r.archetype+' workers';
|
||||
el.appendChild(a);
|
||||
});
|
||||
var g=document.createElement('div');g.className='alrt g';g.textContent='✓ All systems online';
|
||||
el.appendChild(g);
|
||||
}).catch(function(){});
|
||||
}
|
||||
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);
|
||||
|
||||
function renderPipeline(today) {
|
||||
var el = document.getElementById('pipeline');
|
||||
el.textContent = '';
|
||||
var u=0,f=0,fl=0,t=0;
|
||||
if (today&&today.contracts) today.contracts.forEach(function(c){
|
||||
t++;if(c.priority==='urgent')u++;if(c.filled>=c.headcount)fl++;else f++;
|
||||
});
|
||||
[['red',u,'Urgent'],['yel',f,'Filling'],['blu',t,'Contracts'],['grn',fl,'Filled']].forEach(function(p){
|
||||
var d=document.createElement('div');d.className='pip '+p[0];
|
||||
var n=document.createElement('div');n.className='n';n.textContent=p[1];
|
||||
var l=document.createElement('div');l.className='l';l.textContent=p[2];
|
||||
d.appendChild(n);d.appendChild(l);el.appendChild(d);
|
||||
});
|
||||
}
|
||||
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 parseWorker(text) {
|
||||
var parts = (text||'').split(/—|–/);
|
||||
var name = parts[0] ? parts[0].trim() : '?';
|
||||
var rest = parts[1] ? parts[1].trim() : '';
|
||||
var rm = rest.match(/^(.+?) in (.+?)\./);
|
||||
var sm = rest.match(/Skills: ([^.]+)/);
|
||||
var cm = rest.match(/Certs?: ([^.]+)/);
|
||||
var rr = rest.match(/Reliability: ([\d.]+)/);
|
||||
var av = rest.match(/Availability: ([\d.]+)/);
|
||||
var ar = rest.match(/Archetype: (\w+)/);
|
||||
return {
|
||||
name: name, role: rm?rm[1]:'', location: rm?rm[2]:'',
|
||||
skills: sm?sm[1].split('|').slice(0,4):[],
|
||||
certs: cm?cm[1].split('|').filter(function(c){return c!=='none'}):[],
|
||||
reliability: rr?parseFloat(rr[1]):0,
|
||||
availability: av?parseFloat(av[1]):0,
|
||||
archetype: ar?ar[1]:''
|
||||
};
|
||||
}
|
||||
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 renderContracts(today) {
|
||||
var el = document.getElementById('contracts');
|
||||
el.textContent = '';
|
||||
if (!today||!today.contracts||!today.contracts.length) { el.textContent='No contracts'; return; }
|
||||
var sorted = today.contracts.slice().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);
|
||||
});
|
||||
sorted.forEach(function(c) {
|
||||
var isFilled = c.filled >= c.headcount;
|
||||
var card = document.createElement('div'); card.className = 'contract';
|
||||
|
||||
// Header
|
||||
var hdr = document.createElement('div'); hdr.className = 'hdr';
|
||||
var left = document.createElement('div'); left.className = 'left';
|
||||
var dot = document.createElement('div'); dot.className = 'urgency ' + (isFilled?'filled':c.priority);
|
||||
var cl = document.createElement('span'); cl.className = 'client'; cl.textContent = c.client;
|
||||
var need = document.createElement('span'); need.className = 'need';
|
||||
need.textContent = c.role + ' × ' + c.headcount + ' · ' + (c.city||c.state) + ' · ' + c.start;
|
||||
left.appendChild(dot); left.appendChild(cl); left.appendChild(need);
|
||||
|
||||
var right = document.createElement('div'); right.className = 'right';
|
||||
var prog = document.createElement('div'); prog.className = 'progress';
|
||||
var fill = document.createElement('div'); fill.className = 'fill ' + (isFilled?'full':c.filled>0?'partial':'empty');
|
||||
fill.style.width = Math.round(c.filled/c.headcount*100) + '%';
|
||||
prog.appendChild(fill);
|
||||
var cnt = document.createElement('span'); cnt.className = 'count'; cnt.textContent = c.filled + '/' + c.headcount;
|
||||
right.appendChild(prog); right.appendChild(cnt);
|
||||
hdr.appendChild(left); hdr.appendChild(right);
|
||||
card.appendChild(hdr);
|
||||
|
||||
// Workers
|
||||
if (c.matches && c.matches.length) {
|
||||
var wdiv = document.createElement('div'); wdiv.className = 'workers';
|
||||
c.matches.slice(0, c.headcount).forEach(function(m, idx) {
|
||||
var w = parseWorker(m.chunk_text || '');
|
||||
var wcard = document.createElement('div'); wcard.className = 'worker';
|
||||
|
||||
// Avatar
|
||||
var av = document.createElement('div');
|
||||
av.className = 'avatar ' + colors[idx % colors.length];
|
||||
av.textContent = (w.name||'?').split(' ').map(function(n){return n[0]||''}).join('').substring(0,2);
|
||||
wcard.appendChild(av);
|
||||
|
||||
// Info
|
||||
var info = document.createElement('div'); info.className = 'info';
|
||||
var nameRow = document.createElement('div'); nameRow.className = 'name-row';
|
||||
var nm = document.createElement('span'); nm.className = 'wname'; nm.textContent = w.name || m.name || m.doc_id;
|
||||
var loc = document.createElement('span'); loc.className = 'wloc'; loc.textContent = w.location;
|
||||
nameRow.appendChild(nm); nameRow.appendChild(loc);
|
||||
info.appendChild(nameRow);
|
||||
|
||||
// Tags
|
||||
var tags = document.createElement('div'); tags.className = 'tags';
|
||||
w.skills.slice(0,3).forEach(function(s) {
|
||||
var t = document.createElement('span'); t.className = 'tag skill'; t.textContent = s.trim(); tags.appendChild(t);
|
||||
});
|
||||
w.certs.slice(0,2).forEach(function(c) {
|
||||
var t = document.createElement('span'); t.className = 'tag cert'; t.textContent = c.trim(); tags.appendChild(t);
|
||||
});
|
||||
if (w.archetype) {
|
||||
var t = document.createElement('span'); t.className = 'tag type'; t.textContent = w.archetype; tags.appendChild(t);
|
||||
}
|
||||
info.appendChild(tags);
|
||||
|
||||
// Metrics
|
||||
var metrics = document.createElement('div'); metrics.className = 'metrics';
|
||||
addMetric(metrics, 'Reliability', w.reliability);
|
||||
addMetric(metrics, 'Availability', w.availability);
|
||||
info.appendChild(metrics);
|
||||
wcard.appendChild(info);
|
||||
|
||||
// Actions
|
||||
var actions = document.createElement('div'); actions.className = 'actions';
|
||||
var call = document.createElement('button'); call.className = 'wbtn primary'; call.textContent = 'Call';
|
||||
var sms = document.createElement('button'); sms.className = 'wbtn'; sms.textContent = 'SMS';
|
||||
actions.appendChild(call); actions.appendChild(sms);
|
||||
wcard.appendChild(actions);
|
||||
|
||||
wdiv.appendChild(wcard);
|
||||
});
|
||||
card.appendChild(wdiv);
|
||||
}
|
||||
el.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
function addMetric(parent, label, val) {
|
||||
var m = document.createElement('div'); m.className = 'metric';
|
||||
var l = document.createElement('span'); l.className = 'label'; l.textContent = label;
|
||||
var bar = document.createElement('div'); bar.className = 'bar';
|
||||
var v = document.createElement('div');
|
||||
v.className = 'val ' + (val >= 0.8 ? 'high' : val >= 0.5 ? 'med' : 'low');
|
||||
v.style.width = Math.round(val * 100) + '%';
|
||||
bar.appendChild(v);
|
||||
var pct = document.createElement('span'); pct.className = 'pct';
|
||||
pct.style.color = val >= 0.8 ? '#3fb950' : val >= 0.5 ? '#d29922' : '#f85149';
|
||||
pct.textContent = Math.round(val * 100) + '%';
|
||||
m.appendChild(l); m.appendChild(bar); m.appendChild(pct);
|
||||
parent.appendChild(m);
|
||||
}
|
||||
|
||||
function doSearch() {
|
||||
var q=document.getElementById('sq').value.trim();if(!q)return;
|
||||
var st=document.getElementById('sst').value;
|
||||
var rl=document.getElementById('srl').value;
|
||||
var out=document.getElementById('sresults');
|
||||
out.textContent=''; var ld=document.createElement('div');ld.className='loading';ld.textContent='Searching...';out.appendChild(ld);
|
||||
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 sources=d.sources||[];if(!sources.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 → '+sources.length+' best ('+(d.duration_ms||0)+'ms)';out.appendChild(h);
|
||||
sources.forEach(function(s,idx){
|
||||
var w=parseWorker(s.chunk_text);
|
||||
var wcard=document.createElement('div');wcard.className='worker';wcard.style.cssText='background:#161b22;border:1px solid #21262d;border-radius:8px;padding:10px;margin-bottom:6px';
|
||||
var av=document.createElement('div');av.className='avatar '+colors[idx%colors.length];
|
||||
av.textContent=(w.name||'?').split(' ').map(function(n){return n[0]||''}).join('').substring(0,2);wcard.appendChild(av);
|
||||
var info=document.createElement('div');info.className='info';
|
||||
var nr=document.createElement('div');nr.className='name-row';
|
||||
var nm=document.createElement('span');nm.className='wname';nm.textContent=w.name;
|
||||
var loc=document.createElement('span');loc.className='wloc';loc.textContent=w.location;
|
||||
nr.appendChild(nm);nr.appendChild(loc);info.appendChild(nr);
|
||||
var tags=document.createElement('div');tags.className='tags';
|
||||
w.skills.slice(0,3).forEach(function(sk){var t=document.createElement('span');t.className='tag skill';t.textContent=sk.trim();tags.appendChild(t)});
|
||||
w.certs.slice(0,2).forEach(function(c){var t=document.createElement('span');t.className='tag cert';t.textContent=c.trim();tags.appendChild(t)});
|
||||
info.appendChild(tags);
|
||||
var metrics=document.createElement('div');metrics.className='metrics';
|
||||
addMetric(metrics,'Rel',w.reliability);addMetric(metrics,'Avail',w.availability);
|
||||
info.appendChild(metrics);wcard.appendChild(info);out.appendChild(wcard);
|
||||
});
|
||||
}).catch(function(e){out.textContent='Error: '+e.message});
|
||||
}
|
||||
</script>
|
||||
</body></html>
|
||||
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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user