dataforth/testdatadb UI: resizable inspector + fit-to-width cert + quick-search presets; drop redundant PASS/FAIL selector
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,8 +33,17 @@
|
||||
border-radius:6px;height:30px;padding:0 10px;cursor:pointer}
|
||||
.hbtn:hover{background:var(--hover)}
|
||||
/* ---------- layout ---------- */
|
||||
main{display:grid;grid-template-columns:232px 1fr 420px;min-height:0;height:100%}
|
||||
main{display:grid;grid-template-columns:232px 1fr var(--insp,460px);min-height:0;height:100%}
|
||||
.pane{min-height:0;overflow:auto}
|
||||
/* ---------- presets ---------- */
|
||||
.presets{display:flex;flex-direction:column;gap:5px}
|
||||
.preset{display:flex;align-items:center;gap:7px;width:100%;text-align:left;font:inherit;font-size:12.5px;
|
||||
height:30px;padding:0 9px;border:1px solid var(--border);border-radius:6px;background:#fff;color:var(--ink);cursor:pointer}
|
||||
.preset:hover{background:var(--accent-soft);border-color:#bfdbfe}
|
||||
.preset .pi{width:15px;text-align:center;color:var(--ink-3)}
|
||||
.preset.fam{display:inline-flex;width:auto;height:26px;font-family:var(--mono);font-size:11.5px;padding:0 8px}
|
||||
.preset.fam:disabled,.preset:disabled{opacity:.5;cursor:not-allowed;background:#fff}
|
||||
.famrow{display:flex;flex-wrap:wrap;gap:5px;margin-top:6px}
|
||||
/* ---------- filter rail ---------- */
|
||||
.rail{border-right:1px solid var(--border);background:var(--surface);padding:14px}
|
||||
.rail h3{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-3);margin:18px 0 8px;font-weight:600}
|
||||
@@ -76,7 +85,10 @@
|
||||
border-radius:6px;background:#fff;cursor:pointer;color:var(--ink)}
|
||||
.pager button:disabled{opacity:.4;cursor:default}
|
||||
/* ---------- inspector ---------- */
|
||||
.insp{border-left:1px solid var(--border);background:var(--surface);display:grid;grid-template-rows:auto auto 1fr;min-height:0}
|
||||
.insp{position:relative;border-left:1px solid var(--border);background:var(--surface);display:grid;grid-template-rows:auto auto 1fr;min-height:0}
|
||||
.resizer{position:absolute;left:-3px;top:0;bottom:0;width:7px;cursor:col-resize;z-index:5}
|
||||
.resizer:hover,.resizer.drag{background:linear-gradient(90deg,transparent,var(--accent) 45%,var(--accent) 55%,transparent)}
|
||||
body.resizing{cursor:col-resize;user-select:none}
|
||||
.insp .meta{padding:12px 14px;border-bottom:1px solid var(--border)}
|
||||
.insp .meta .sn{font-family:var(--mono);font-size:16px;font-weight:700}
|
||||
.insp .meta dl{display:grid;grid-template-columns:auto 1fr;gap:3px 12px;margin:10px 0 0;font-size:12.5px}
|
||||
@@ -109,12 +121,9 @@
|
||||
<main>
|
||||
<!-- filter rail -->
|
||||
<aside class="rail pane">
|
||||
<h3>Result</h3>
|
||||
<div class="seg" id="segResult">
|
||||
<button data-v="" class="on">All</button>
|
||||
<button data-v="PASS">PASS</button>
|
||||
<button data-v="FAIL">FAIL</button>
|
||||
</div>
|
||||
<h3>Quick searches</h3>
|
||||
<div class="presets" id="presets"></div>
|
||||
<div class="famrow" id="families"></div>
|
||||
<h3>Test date</h3>
|
||||
<label>From</label><input type="date" id="fFrom">
|
||||
<label>To</label><input type="date" id="fTo">
|
||||
@@ -157,6 +166,7 @@
|
||||
|
||||
<!-- inspector -->
|
||||
<aside class="insp">
|
||||
<div class="resizer" id="resizer" title="Drag to resize"></div>
|
||||
<div class="meta" id="meta"><div class="empty" style="height:auto;padding:8px 0">No record selected</div></div>
|
||||
<div class="acts" id="acts" style="display:none"></div>
|
||||
<div id="viewer"><div class="empty">Search a serial number, then select a row.<br>The calibration certificate renders here.</div></div>
|
||||
@@ -240,15 +250,14 @@ function select(id){
|
||||
<button onclick="document.getElementById('viewer').querySelector('iframe').contentWindow.print()">Print</button>
|
||||
<a href="${ds}?format=txt" download="${r.serial_number}.txt">TXT</a>
|
||||
<a href="${ds}?format=html" download="${r.serial_number}.html">HTML</a>`;
|
||||
$('viewer').innerHTML = `<iframe src="${ds}?format=html" title="datasheet"></iframe>`;
|
||||
$('viewer').innerHTML = '<iframe id="dsframe" title="datasheet"></iframe>';
|
||||
const _f=document.getElementById('dsframe'); _f.onload=fitCert; _f.src=ds+'?format=html';
|
||||
syncUrl();
|
||||
}
|
||||
|
||||
// ---- filters wiring ----
|
||||
function debouncedSearch(){ clearTimeout(timer); timer=setTimeout(()=>{state.page=0;search();},280); }
|
||||
$('omni').addEventListener('input', e=>{ routeOmni(e.target.value); debouncedSearch(); });
|
||||
$('segResult').addEventListener('click', e=>{ const b=e.target.closest('button'); if(!b)return;
|
||||
[...e.currentTarget.children].forEach(x=>x.classList.toggle('on',x===b)); state.result=b.dataset.v; state.page=0; search(); });
|
||||
$('fFrom').onchange=e=>{state.from=e.target.value;state.page=0;search();};
|
||||
$('fTo').onchange=e=>{state.to=e.target.value;state.page=0;search();};
|
||||
$('fModel').onchange=e=>{state.model=e.target.value;state.page=0;search();};
|
||||
@@ -257,8 +266,58 @@ $('fLog').onchange=e=>{state.logtype=e.target.value;state.page=0;search();};
|
||||
$('pageSize').onchange=e=>{state.size=+e.target.value;state.page=0;search();};
|
||||
$('prev').onclick=()=>{if(state.page>0){state.page--;search();$('twrap').scrollTop=0;}};
|
||||
$('next').onclick=()=>{state.page++;search();$('twrap').scrollTop=0;};
|
||||
$('reset').onclick=()=>{ ['result','station','logtype','from','to'].forEach(k=>state[k]='');
|
||||
$('segResult').children[0].click(); $('fFrom').value=$('fTo').value=$('fStation').value=$('fLog').value=''; };
|
||||
$('reset').onclick=()=>{ ['serial','model','q','result','station','logtype','from','to'].forEach(k=>state[k]='');
|
||||
$('omni').value='';$('route').textContent='';$('fFrom').value=$('fTo').value=$('fStation').value=$('fLog').value=$('fModel').value='';
|
||||
state.page=0; search(); };
|
||||
|
||||
// ---- cert fit-to-width (same-origin: scale the rendered cert so it never side-scrolls) ----
|
||||
function fitCert(){
|
||||
const f=document.getElementById('dsframe'); if(!f) return;
|
||||
try{ const doc=f.contentDocument; if(!doc) return;
|
||||
const root=doc.documentElement; root.style.zoom='';
|
||||
const natural=Math.max(doc.body?doc.body.scrollWidth:0, root.scrollWidth);
|
||||
const avail=f.clientWidth-12;
|
||||
root.style.zoom = (natural>avail) ? Math.max(0.45, avail/natural) : 1;
|
||||
}catch(e){}
|
||||
}
|
||||
window.addEventListener('resize', fitCert);
|
||||
|
||||
// ---- resizable inspector ----
|
||||
(function(){ const rz=$('resizer'); let on=false;
|
||||
rz.addEventListener('mousedown', e=>{ on=true; rz.classList.add('drag'); document.body.classList.add('resizing'); e.preventDefault(); });
|
||||
window.addEventListener('mousemove', e=>{ if(!on) return;
|
||||
let w=Math.max(340, Math.min(window.innerWidth-560, window.innerWidth-e.clientX));
|
||||
document.documentElement.style.setProperty('--insp', w+'px'); });
|
||||
window.addEventListener('mouseup', ()=>{ if(on){ on=false; rz.classList.remove('drag'); document.body.classList.remove('resizing'); fitCert(); } });
|
||||
})();
|
||||
|
||||
// ---- quick-search presets ----
|
||||
function clearAll(){ ['serial','model','q','result','station','logtype','from','to'].forEach(k=>state[k]='');
|
||||
$('omni').value='';$('route').textContent='';$('fFrom').value=$('fTo').value=$('fStation').value=$('fLog').value=$('fModel').value=''; }
|
||||
function applyPreset(fn){ clearAll(); fn(); state.page=0; search(); }
|
||||
const _iso=d=>d.toISOString().slice(0,10);
|
||||
const PRESETS=[
|
||||
{ic:'◷',label:'Recent',fn:()=>{}},
|
||||
{ic:'✕',label:'Failures',fn:()=>{state.result='FAIL';}},
|
||||
{ic:'•',label:'Today',fn:()=>{const t=_iso(new Date());state.from=t;state.to=t;}},
|
||||
{ic:'7',label:'Last 7 days',fn:()=>{const d=new Date();d.setDate(d.getDate()-7);state.from=_iso(d);}},
|
||||
{ic:'∷',label:'This year',fn:()=>{state.from=new Date().getFullYear()+'-01-01';}},
|
||||
];
|
||||
const SOON=[
|
||||
{label:'Latest upload batch',why:'needs an upload-time sort param in /api/search'},
|
||||
{label:'Retested units',why:'needs a retest flag in the pipeline'},
|
||||
{label:'Not yet published',why:'needs a published filter in /api/search'},
|
||||
];
|
||||
function renderPresets(){
|
||||
const host=$('presets'); host.innerHTML='';
|
||||
PRESETS.forEach(p=>{ const b=document.createElement('button'); b.className='preset';
|
||||
b.innerHTML='<span class="pi">'+p.ic+'</span>'+p.label; b.onclick=()=>applyPreset(p.fn); host.appendChild(b); });
|
||||
SOON.forEach(s=>{ const b=document.createElement('button'); b.className='preset'; b.disabled=true; b.title=s.why;
|
||||
b.innerHTML='<span class="pi">…</span>'+s.label; host.appendChild(b); });
|
||||
const fam=$('families'); fam.innerHTML='';
|
||||
['DSCA','8B','5B','7B','SCM5B'].forEach(m=>{ const b=document.createElement('button'); b.className='preset fam';
|
||||
b.textContent=m; b.onclick=()=>applyPreset(()=>{state.model=m;$('fModel').value=m;}); fam.appendChild(b); });
|
||||
}
|
||||
|
||||
// ---- keyboard ----
|
||||
document.addEventListener('keydown', e=>{
|
||||
@@ -292,6 +351,7 @@ async function boot(){
|
||||
alert('Total: '+s.total_records.toLocaleString()+'\nResult: '+(s.by_result||[]).map(r=>r.overall_result+' '+r.count.toLocaleString()).join(' · ')
|
||||
+'\nDates: '+fmtDate(s.date_range.oldest)+' → '+fmtDate(s.date_range.newest)
|
||||
+'\nLog types: '+(s.by_log_type||[]).map(r=>r.log_type+' '+r.count.toLocaleString()).join(' · ')); };
|
||||
renderPresets();
|
||||
$('omni').focus();
|
||||
search();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user