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:
parent
6b679152bc
commit
cba339151f
@ -156,6 +156,7 @@ class CrateEditIn(BaseModel):
|
|||||||
name: str | None = None
|
name: str | None = None
|
||||||
label_text: str | None = None
|
label_text: str | None = None
|
||||||
crate_purpose: 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}")
|
@router.post("/crate/{crate_id}")
|
||||||
|
|||||||
@ -112,6 +112,18 @@
|
|||||||
</div>
|
</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 id="ccList" style="max-height:42vh;overflow:auto;margin-top:8px"><div class="muted">pick a crate on the map →</div></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="panel">
|
||||||
<div class="bar" style="justify-content:space-between"><b style="font-size:13px">💿 Release Info</b>
|
<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>
|
<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 ─────────────
|
// ───────────── STORE VIEW ─────────────
|
||||||
async function toStore(){
|
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';
|
$('#lvlCtl').style.display='none'; $('#rackEditBtn').style.display='none';
|
||||||
$('#crumb').textContent='Store map';
|
$('#crumb').textContent='Store map';
|
||||||
const sid=$('#spaceSel').value;
|
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="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>'
|
<td class="pink">${money(i.price)}</td></tr>`).join('')+'</tbody></table>'
|
||||||
: '<div class="muted">empty crate</div>';
|
: '<div class="muted">empty crate</div>';
|
||||||
setViewBtns();
|
setViewBtns(); renderCrateInfo();
|
||||||
if(ST.tab==='scanner') renderScanner();
|
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){
|
async function compressCrate(id){
|
||||||
if(!confirm('Renumber this crate\'s slots 1..N (close gaps)?')) return;
|
if(!confirm('Renumber this crate\'s slots 1..N (close gaps)?')) return;
|
||||||
await fetch('/nav/reorg/compress/'+id,{method:'POST',headers:hdr()}); loadCrate(id);
|
await fetch('/nav/reorg/compress/'+id,{method:'POST',headers:hdr()}); loadCrate(id);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user