feat(nav): Crate Info panel — edit label_text + rotate crate.direction in /search

Per-crate panel under Crate Contents: rename (label_text) + a Facing segment
(↑back/↓front/←left/→right) that rotates the crate live — the map arrow flips on
click. CrateEditIn gained 'direction'. Restores the crate editing my tabbed rewrite
dropped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-06-23 10:51:41 +10:00
parent 6b679152bc
commit cba339151f
2 changed files with 41 additions and 2 deletions

View File

@ -156,6 +156,7 @@ class CrateEditIn(BaseModel):
name: str | None = None
label_text: str | None = None
crate_purpose: str | None = None
direction: str | None = None # forward | back | left | right — the crate's facing (rotate)
@router.post("/crate/{crate_id}")

View File

@ -112,6 +112,18 @@
</div>
<div id="ccList" style="max-height:42vh;overflow:auto;margin-top:8px"><div class="muted">pick a crate on the map →</div></div>
</div>
<div class="panel" id="crateInfoPanel" style="display:none">
<b style="font-size:13px">📦 Crate Info</b>
<div class="bar" style="margin-top:8px"><input id="ciName" placeholder="crate name / label" style="flex:1"><button class="ghost" onclick="crateRename()">Save name</button></div>
<div class="bar" style="margin-top:8px;align-items:center"><span class="muted">Facing</span>
<span class="seg" id="ciFacing">
<button data-d="back" onclick="crateRotate('back')">↑ back</button>
<button data-d="forward" onclick="crateRotate('forward')">↓ front</button>
<button data-d="left" onclick="crateRotate('left')">← left</button>
<button data-d="right" onclick="crateRotate('right')">→ right</button>
</span>
<span id="ciSaved" class="ok" style="font-size:12px"></span></div>
</div>
<div class="panel">
<div class="bar" style="justify-content:space-between"><b style="font-size:13px">💿 Release Info</b>
<div class="bar"><input id="relQ" placeholder="release id / sku" style="width:130px"><button class="ghost" onclick="relLookup()"></button></div></div>
@ -194,7 +206,7 @@ function redraw(){ if(ST.view==='store') drawStore(); else if(ST.rack) drawRack(
// ───────────── STORE VIEW ─────────────
async function toStore(){
ST.view='store'; ST.rack=null; ST.selCrate=null;
ST.view='store'; ST.rack=null; ST.selCrate=null; ST.crateInfo=null; renderCrateInfo();
$('#lvlCtl').style.display='none'; $('#rackEditBtn').style.display='none';
$('#crumb').textContent='Store map';
const sid=$('#spaceSel').value;
@ -317,9 +329,35 @@ async function loadCrate(id){
<td class="muted">${esc(i.artist||'')}</td><td class="muted" style="font-size:11px">${esc(i.genre||'')}${i.style?' · '+esc(i.style):''}</td>
<td class="pink">${money(i.price)}</td></tr>`).join('')+'</tbody></table>'
: '<div class="muted">empty crate</div>';
setViewBtns();
setViewBtns(); renderCrateInfo();
if(ST.tab==='scanner') renderScanner();
}
function renderCrateInfo(){
const c=ST.crateInfo, p=$('#crateInfoPanel'); if(!p) return;
if(!c){ p.style.display='none'; return; }
p.style.display='';
$('#ciName').value=c.label_text||c.name||'';
const d=(c.direction||'').toLowerCase();
document.querySelectorAll('#ciFacing button').forEach(b=>b.classList.toggle('on', b.dataset.d===d));
$('#ciSaved').textContent='';
}
async function crateRotate(dir){
if(!ST.selCrate) return;
await post('/nav/crate/'+ST.selCrate,{direction:dir});
ST.crateInfo.direction=dir;
const c=(ST.crates||[]).find(x=>x.id===ST.selCrate); if(c) c.direction=dir;
renderCrateInfo(); redraw(); // facing arrow flips live on the map
$('#ciSaved').textContent='✓ rotated';
}
async function crateRename(){
if(!ST.selCrate) return;
const v=$('#ciName').value.trim();
await post('/nav/crate/'+ST.selCrate,{label_text:v});
ST.crateInfo.label_text=v;
const c=(ST.crates||[]).find(x=>x.id===ST.selCrate); if(c) c.label_text=v;
$('#ccName').innerHTML='📦 '+esc(v||('Crate '+ST.selCrate));
$('#ciSaved').textContent='✓ saved'; redraw(); // crate label updates on the map
}
async function compressCrate(id){
if(!confirm('Renumber this crate\'s slots 1..N (close gaps)?')) return;
await fetch('/nav/reorg/compress/'+id,{method:'POST',headers:hdr()}); loadCrate(id);