Redesign Breadcrumbs panel: list + detail split view (option 3)
This commit is contained in:
@@ -124,6 +124,19 @@
|
||||
.bc-related summary{cursor:pointer; color:var(--interactive);}
|
||||
.bc-related li{margin-left:22px; padding:2px 0; color:var(--text-secondary);}
|
||||
.bc-foot{display:flex; align-items:center; gap:14px; margin-top:12px; flex-wrap:wrap;}
|
||||
.bc-layout{display:grid; grid-template-columns:250px 1fr; gap:16px; align-items:start;}
|
||||
.bc-nav{background:var(--layer); border:1px solid var(--border); position:sticky; top:12px; max-height:80vh; overflow-y:auto;}
|
||||
.bc-nav-item{display:flex; align-items:center; gap:9px; padding:10px 12px; border-bottom:1px solid var(--border); cursor:pointer;}
|
||||
.bc-nav-item:last-child{border-bottom:none;}
|
||||
.bc-nav-item:hover{background:var(--layer-hover);}
|
||||
.bc-nav-item.active{background:var(--layer-hover); border-left:3px solid var(--interactive); padding-left:9px;}
|
||||
.bc-nav-text{flex:1; font-size:12.5px; line-height:1.35; color:var(--text-secondary);}
|
||||
.bc-nav-item.active .bc-nav-text{color:var(--text); font-weight:600;}
|
||||
.bc-dot{width:8px; height:8px; border-radius:50%; flex-shrink:0; background:var(--border-strong);}
|
||||
.bc-dot.complete{background:var(--success);}
|
||||
.bc-dot.partial{background:var(--warning);}
|
||||
.bc-detail{min-width:0;}
|
||||
@media (max-width:900px){ .bc-layout{grid-template-columns:1fr;} .bc-nav{position:static; max-height:none;} }
|
||||
.gate{display:flex; gap:12px; font-size:12.5px; flex-wrap:wrap;}
|
||||
.gate span{color:var(--text-helper);}
|
||||
.gate span.ok{color:var(--success); font-weight:600;}
|
||||
@@ -205,7 +218,10 @@
|
||||
<p>Root problem to value driver to capability to measurable outcome. A completed breadcrumb IS a pilot success criterion; the export builds the criteria table from every complete card. AI draft fills the fields from construction execution methodology (EVM, CII AWP, Last Planner, first-time quality); the room reviews and edits.</p>
|
||||
</div>
|
||||
<div class="summary-bar" id="bcSummary"></div>
|
||||
<div id="bcList"></div>
|
||||
<div class="bc-layout">
|
||||
<nav class="bc-nav" id="bcNav"></nav>
|
||||
<div class="bc-detail" id="bcDetail"></div>
|
||||
</div>
|
||||
<div class="add-row"><button class="btn ghost" id="btnAddBc">Add a blank breadcrumb</button></div>
|
||||
</section>
|
||||
|
||||
@@ -478,7 +494,9 @@ document.getElementById('panel-map').addEventListener('click', e=>{
|
||||
const c = clusterById(t.dataset.cprom);
|
||||
if(c){
|
||||
c.promoted = true;
|
||||
S.breadcrumbs.push(B({prob:(c.root||c.name), related:activeMembers(c.id).map(p=>p.t)}));
|
||||
const nb = B({prob:(c.root||c.name), related:activeMembers(c.id).map(p=>p.t)});
|
||||
S.breadcrumbs.push(nb);
|
||||
bcSelectedId = nb.id;
|
||||
save(); renderAll(); toast('Promoted with ' + activeMembers(c.id).length + ' related issues');
|
||||
}
|
||||
return;
|
||||
@@ -567,6 +585,8 @@ document.getElementById('newCluster').addEventListener('keydown', e=>{ if(e.key=
|
||||
const ADOPTION_WORDS = /implement|adopt|roll ?out|have a tool|tool exists|usage|active users?|users? active/i;
|
||||
const REQ = ['driver','cap','metric','method','baseline'];
|
||||
function bcComplete(b){ return REQ.every(f=>String(b[f]||'').trim().length>0); }
|
||||
function bcStatus(b){ if(bcComplete(b)) return 'complete'; if(REQ.some(f=>String(b[f]||'').trim())) return 'partial'; return 'blank'; }
|
||||
let bcSelectedId = null;
|
||||
function footHTML(b){
|
||||
const adoptionFlag = b.type==='Outcome' && ADOPTION_WORDS.test(b.metric||'');
|
||||
const gate = REQ.map(f=>{
|
||||
@@ -587,12 +607,7 @@ function bcSummaryHTML(){
|
||||
<div class="${nInc?'warn':''}"><b>${nInc}</b>Incomplete</div>
|
||||
<div><b>${nOut}</b>Outcome</div><div><b>${S.breadcrumbs.length-nOut}</b>Adoption</div>`;
|
||||
}
|
||||
function renderBc(){
|
||||
const wrap = document.getElementById('bcList');
|
||||
document.getElementById('cntBc').textContent = S.breadcrumbs.length;
|
||||
document.getElementById('bcSummary').innerHTML = bcSummaryHTML();
|
||||
if(!S.breadcrumbs.length){ wrap.innerHTML = '<div class="empty">Promote root problems from the map, or add a blank breadcrumb.</div>'; return; }
|
||||
wrap.innerHTML = S.breadcrumbs.map(b=>{
|
||||
function bcCardHTML(b){
|
||||
const rel = (b.related && b.related.length)
|
||||
? `<details class="bc-related"><summary>Related issues carried from the cluster (${b.related.length})</summary><ul>${b.related.map(r=>`<li>${esc(r)}</li>`).join('')}</ul></details>` : '';
|
||||
return `<div class="bc${b.example?' example':''}">
|
||||
@@ -620,20 +635,48 @@ function renderBc(){
|
||||
</div>
|
||||
<div class="bc-foot" data-foot="${b.id}">${footHTML(b)}</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
document.getElementById('bcList').addEventListener('input', e=>{
|
||||
function bcNavRowHTML(b){
|
||||
const label = shortT(b.prob, 60) || '(blank breadcrumb)';
|
||||
return `<div class="bc-nav-item${b.id===bcSelectedId?' active':''}" data-bcnav="${b.id}">
|
||||
<span class="bc-dot ${bcStatus(b)}"></span>
|
||||
<span class="bc-nav-text">${esc(label)}</span>
|
||||
</div>`;
|
||||
}
|
||||
function renderBc(){
|
||||
document.getElementById('cntBc').textContent = S.breadcrumbs.length;
|
||||
document.getElementById('bcSummary').innerHTML = bcSummaryHTML();
|
||||
const nav = document.getElementById('bcNav');
|
||||
const detail = document.getElementById('bcDetail');
|
||||
if(!S.breadcrumbs.length){
|
||||
nav.innerHTML = '';
|
||||
detail.innerHTML = '<div class="empty">Promote root problems from the map, or add a blank breadcrumb.</div>';
|
||||
bcSelectedId = null;
|
||||
return;
|
||||
}
|
||||
if(!S.breadcrumbs.some(b=>b.id===bcSelectedId)) bcSelectedId = S.breadcrumbs[0].id;
|
||||
nav.innerHTML = S.breadcrumbs.map(bcNavRowHTML).join('');
|
||||
const selected = S.breadcrumbs.find(b=>b.id===bcSelectedId);
|
||||
detail.innerHTML = bcCardHTML(selected);
|
||||
}
|
||||
document.getElementById('bcNav').addEventListener('click', e=>{
|
||||
const row = e.target.closest('[data-bcnav]');
|
||||
if(row){ bcSelectedId = row.dataset.bcnav; renderBc(); }
|
||||
});
|
||||
document.getElementById('bcDetail').addEventListener('input', e=>{
|
||||
if(e.target.dataset.bid){
|
||||
const b = S.breadcrumbs.find(x=>x.id===e.target.dataset.bid);
|
||||
if(b){
|
||||
b[e.target.dataset.f] = e.target.value; save();
|
||||
const foot = document.querySelector(`[data-foot="${b.id}"]`);
|
||||
if(foot) foot.innerHTML = footHTML(b);
|
||||
const navRow = document.querySelector(`[data-bcnav="${b.id}"]`);
|
||||
if(navRow) navRow.outerHTML = bcNavRowHTML(b);
|
||||
document.getElementById('bcSummary').innerHTML = bcSummaryHTML();
|
||||
}
|
||||
}
|
||||
});
|
||||
document.getElementById('bcList').addEventListener('click', e=>{
|
||||
document.getElementById('bcDetail').addEventListener('click', e=>{
|
||||
const tt = e.target.closest('[data-tt]');
|
||||
if(tt){
|
||||
const b = S.breadcrumbs.find(x=>x.id===tt.dataset.tt);
|
||||
@@ -647,7 +690,9 @@ document.getElementById('bcList').addEventListener('click', e=>{
|
||||
}
|
||||
});
|
||||
document.getElementById('btnAddBc').addEventListener('click', ()=>{
|
||||
S.breadcrumbs.push(B({}));
|
||||
const nb = B({});
|
||||
S.breadcrumbs.push(nb);
|
||||
bcSelectedId = nb.id;
|
||||
save(); renderBc();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user