From 1d004cab754426f0b35e982b61faf686bc4013a6 Mon Sep 17 00:00:00 2001 From: "n.siegfried" Date: Mon, 3 Aug 2026 12:59:34 -0700 Subject: [PATCH] Widen the IWP screen and add a left work-package navigator The Work Package form was capped at a 1000px column, which wasted most of a desktop screen, and the only way to reach another package was to scroll to the Saved table at the bottom. - Put the form in a wide two-column shell (max 1760px); ctx-bar, mode-wrap and the release banner widened to match. - Above 1200px the two-up field grids flow to 3-4 columns instead of stretching two fields across the whole card. Narrow layouts are unchanged. - New sticky left rail listing every saved package, grouped by status in field order, with WP number, subject, readiness dot and type. Click to open it in the form; the package being edited is highlighted. Filter box, + New and Dashboard shortcuts, collapsible (state persisted), hidden under 1100px where the Saved table still covers navigation. - The rail re-renders from renderSavedList(), so saves, deletes, splits, archive/restore and the project pull all keep it current. Co-Authored-By: Claude Opus 5 (1M context) --- html/wp-creation-app.js | 63 +++++++++++++++++++++++++++++++++++-- html/wp-creation-index.html | 18 +++++++++++ html/wp-creation-styles.css | 61 ++++++++++++++++++++++++++++++++--- 3 files changed, 135 insertions(+), 7 deletions(-) diff --git a/html/wp-creation-app.js b/html/wp-creation-app.js index be40b13..35d2b05 100644 --- a/html/wp-creation-app.js +++ b/html/wp-creation-app.js @@ -844,6 +844,7 @@ function setFormChrome(on){ if(save) save.style.display = on ? 'flex' : 'none'; document.body.classList.toggle('has-sticky-save', !!on); if(on){ buildSectionNav(); updateStickyStatus(); makeCollapsible(); initSectionNavAutoHide(); } + else positionSectionNav(); } // Make each form card collapsible by clicking its heading (idempotent). function makeCollapsible(){ @@ -881,6 +882,9 @@ function buildSectionNav(){ function positionSectionNav(){ const nav=document.getElementById('section-nav'), hdr=document.querySelector('.header'); if(nav && hdr) nav.style.top = hdr.offsetHeight + 'px'; + // The WP navigator rail sticks below the header + section-nav chrome. + const top=(hdr?hdr.offsetHeight:0)+((nav && nav.style.display!=='none')?nav.offsetHeight:0); + document.documentElement.style.setProperty('--rail-top', top+'px'); } let _snLastY=0, _snBound=false; function initSectionNavAutoHide(){ @@ -912,6 +916,7 @@ function saveStore(){ try{ localStorage.setItem(wpKey(STORE_KEY), JSON.stringify function loadStore(){ try{ const d=JSON.parse(localStorage.getItem(wpKey(STORE_KEY))); savedPackages=Array.isArray(d)?d:[]; }catch(e){ savedPackages=[]; } } function renderSavedList(){ const card=document.getElementById('saved-card'), body=document.getElementById('saved-body'); + renderWpNav(); document.getElementById('saved-count').textContent=savedPackages.length?`(${savedPackages.length})`:''; if(!savedPackages.length){ card.style.display='none'; return; } if(document.getElementById('pkg-output').style.display==='none' || document.getElementById('pkg-output').style.display==='') card.style.display=''; @@ -926,6 +931,58 @@ function renderSavedList(){ } function viewPackage(i){ if(savedPackages[i]) renderPackage(savedPackages[i]); } +// ── WP NAVIGATOR (left rail) ───────────────────────────────────────────────── +// Every saved package on this project, grouped by status, filterable. Clicking a +// row opens it in the form (same path as the Saved table's "edit"). +const WPNAV_ORDER=['Issue','In Progress','Issued','QC','Scheduled','Draft','Closed']; +function toggleWpNav(){ + document.body.classList.toggle('wp-nav-collapsed'); + try{ localStorage.setItem('wp_nav_collapsed', document.body.classList.contains('wp-nav-collapsed')?'1':''); }catch(e){} +} +function renderWpNav(){ + const list=document.getElementById('wp-nav-list'); if(!list) return; + const cnt=document.getElementById('wp-nav-count'); + if(cnt) cnt.textContent = savedPackages.length ? '('+savedPackages.length+')' : ''; + const q=((document.getElementById('wp-nav-search')||{}).value||'').trim().toLowerCase(); + // Keep the original index — edit/view act on savedPackages by position. + const rows=savedPackages.map((p,i)=>({p:p,i:i})).filter(r=>{ + if(!q) return true; + const p=r.p; + return [p.number,p.subject,p.type,p.location,p.system].some(v=>String(v||'').toLowerCase().includes(q)); + }); + if(!rows.length){ + list.innerHTML=''; + return; + } + const groups={}; + rows.forEach(r=>{ const s=r.p.status||'Draft'; (groups[s]=groups[s]||[]).push(r); }); + const keys=Object.keys(groups).sort((a,b)=>{ + const ia=WPNAV_ORDER.indexOf(a), ib=WPNAV_ORDER.indexOf(b); + return (ia<0?99:ia)-(ib<0?99:ib); + }); + list.innerHTML=keys.map(k=>{ + const label = k==='Issue' ? 'Issue (Hold)' : k; + return ''+groups[k].map(r=>{ + const p=r.p, open=(p.constraints||[]).filter(c=>c.status==='open').length; + const dot = p.status==='Issue' ? 'hold' : (open===0 ? 'ok' : 'open'); + const state = p.status==='Issue' ? 'on hold' : (open===0 ? 'ready' : open+' open'); + const active = (editingId && p.id===editingId) ? ' active' : ''; + return ''; + }).join(''); + }).join(''); +} +function wpNavOpen(i){ + const p=savedPackages[i]; if(!p) return; + editingId=p.id; + loadPackageIntoForm(p); // ends in showForm(), so this also leaves the dashboard / package view + renderWpNav(); + track('wp_nav_open'); +} + // ── WP HISTORY (audit trail) ───────────────────────────────────────────────── function showHistoryRow(i){ const p=savedPackages[i]; if(p) showHistory(p.id, p.number||p.subject); } function showHistoryCurrent(){ showHistory(editingId, (document.getElementById('wp_number')||{}).value); } @@ -960,7 +1017,7 @@ async function showHistory(wpId, label){ } function deletePackage(i){ const p=savedPackages[i]; if(!p) return; if(!confirm('Delete work package "'+(p.number||p.subject||'untitled')+'"? This cannot be undone.')) return; const delId=p.id; savedPackages.splice(i,1); saveStore(); renderSavedList(); track('package_deleted'); if(typeof ProjectData!=='undefined' && ProjectData.removeWP) ProjectData.removeWP(delId); } function clearSaved(){ if(!savedPackages.length) return; if(!confirm('Delete all '+savedPackages.length+' saved packages?')) return; const ids=savedPackages.map(p=>p.id); savedPackages=[]; saveStore(); renderSavedList(); if(typeof ProjectData!=='undefined' && ProjectData.removeWP) ids.forEach(id=>ProjectData.removeWP(id)); } -function editPackage(i){ const p=savedPackages[i]; if(!p) return; editingId=p.id; loadPackageIntoForm(p); } +function editPackage(i){ const p=savedPackages[i]; if(!p) return; editingId=p.id; loadPackageIntoForm(p); renderWpNav(); } function loadExample(){ loadPackageIntoForm(JSON.parse(JSON.stringify(EXAMPLE_PKG))); editingId=null; toast('Example work package loaded'); track('example_loaded'); } function loadPackageIntoForm(p){ pkgKind = (p.kind === 'ewp') ? 'ewp' : 'iwp'; // set before type/constraint pickers so they filter correctly @@ -1055,7 +1112,7 @@ function newPackage(){ pkgHolds=[]; pkgOverrides={}; set_quality_from_sop(); lockQuality('wp_qc'); lockQuality('wp_photo'); lockQuality('wp_hold'); prevStatus='Draft'; - updateNumber(); updateReleaseBanner(); showForm(); track('new_package'); + updateNumber(); updateReleaseBanner(); showForm(); renderWpNav(); track('new_package'); } function set_quality_from_sop(){ const set=(id,v)=>{const el=document.getElementById(id); if(el) el.value=v||'';}; set('wp_qc',sopValueFor('wp_qc')); set('wp_photo',sopValueFor('wp_photo')); set('wp_hold',sopValueFor('wp_hold')); } function exportPackages(){ @@ -1367,7 +1424,9 @@ function bootData(){ bootSOP(); setRadio('status','Draft'); loadMembers(); + try{ if(localStorage.getItem('wp_nav_collapsed')) document.body.classList.add('wp-nav-collapsed'); }catch(e){} renderSavedList(); + positionSectionNav(); cmtInit(); // Deep-link: open straight to the dashboard when requested (?view=dashboard or #dashboard). const p=new URLSearchParams(location.search); if(p.get('view')==='dashboard' || location.hash==='#dashboard'){ showDashboard(); } diff --git a/html/wp-creation-index.html b/html/wp-creation-index.html index 4d102ad..0f14173 100644 --- a/html/wp-creation-index.html +++ b/html/wp-creation-index.html @@ -45,6 +45,23 @@
+
+ + + + +
@@ -268,6 +285,7 @@
+