Fix SOP→home flow, stop sample-data fallback for real projects, add custom WP types

- SOP Complete now returns to the project home page after the confirmation
  popup (instead of staying on the SOP tab), and stamps projectId onto the SOP.
- WP creator no longer substitutes the Micron SAMPLE_SOP when a project is
  active but its SOP isn't found — it shows a "complete the SOP first" empty
  state instead. Sample is only used for a standalone (no-project) preview.
  This was the source of "loaded with sample data I didn't select."
- SOP WP Types step gains "+ Add Custom Type" (editable name + remove);
  blank-named custom types are dropped from the generated SOP. Custom types
  round-trip via the saved state.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 16:29:14 -07:00
parent 68c1c803d6
commit a18ae487f6
2 changed files with 46 additions and 8 deletions

View File

@@ -381,14 +381,24 @@ function renderWPTypes(){
state.wpTypes.forEach((t,i)=>{
const row = document.createElement('div');
row.className = 'wp-type-row';
const nameCell = t.custom
? `<div style="display:flex; gap:6px; align-items:center;">
<input type="text" placeholder="Custom type name" value="${(t.name||'').replace(/"/g,'&quot;')}" onchange="state.wpTypes[${i}].name=this.value" style="flex:1; padding:0.5rem; border:1px solid var(--border); border-radius:4px; font-weight:600;">
<button onclick="removeWPType(${i})" title="Remove custom type" style="background:var(--danger); color:#fff; border:none; border-radius:4px; width:28px; height:28px; cursor:pointer; font-weight:600; flex:none;">✕</button>
</div>`
: `<div style="font-weight:600;">${t.name}</div>`;
row.innerHTML = `
<div style="font-weight:600;">${t.name}</div>
${nameCell}
<div style="text-align:center;"><input type="checkbox" ${t.enabled?'checked':''} onchange="toggleWPType(${i})" style="width:18px; height:18px; cursor:pointer;"></div>
<input type="text" placeholder="Special rules…" value="${(t.notes||'').replace(/"/g,'&quot;')}" onchange="state.wpTypes[${i}].notes=this.value" style="padding:0.5rem; border:1px solid var(--border); border-radius:4px;">
<input type="text" placeholder="PM / CM / QC…" value="${(t.approval||'').replace(/"/g,'&quot;')}" onchange="state.wpTypes[${i}].approval=this.value" style="padding:0.5rem; border:1px solid var(--border); border-radius:4px;">
`;
container.appendChild(row);
});
const addRow = document.createElement('div');
addRow.style.cssText = 'margin-top:0.85rem;';
addRow.innerHTML = `<button onclick="addCustomWPType()" style="background:var(--primary,#0f62fe); color:#fff; border:none; padding:0.55rem 1rem; border-radius:4px; font-weight:600; cursor:pointer; font-size:13px;">+ Add Custom Type</button>`;
container.appendChild(addRow);
}
function toggleWPType(i){
@@ -396,6 +406,21 @@ function toggleWPType(i){
renderWPTypes();
}
function addCustomWPType(){
state.wpTypes.push({name:'', enabled:true, notes:'', approval:'', custom:true});
renderWPTypes();
// Focus the new custom row's name input.
const rows = document.querySelectorAll('#wp-types-table .wp-type-row');
const last = rows[rows.length-1];
const nameInput = last && last.querySelector('input[type="text"]');
if(nameInput) nameInput.focus();
}
function removeWPType(i){
state.wpTypes.splice(i,1);
renderWPTypes();
}
function renderTeamMembers(){
const container = document.getElementById('team-members-list');
if(!container) return;
@@ -717,8 +742,8 @@ function completeSOP(){
instanceSuffix: state.governance.instanceSuffix || 'letter',
sizeHoursMax: state.governance.sizeHoursMax || ''
},
woTypes: state.wpTypes.filter(t=>t.enabled).map(t=>({
name: t.name,
woTypes: state.wpTypes.filter(t=>t.enabled && (t.name||'').trim()).map(t=>({
name: t.name.trim(),
enabled: true,
notes: t.notes || '',
approval: t.approval || ''
@@ -741,6 +766,8 @@ function completeSOP(){
};
sopComplete = true;
// Stamp the active project onto the SOP so it's unambiguously tied to it.
try { if(typeof ProjectData!=='undefined' && ProjectData.getActiveId()) sop.projectId = ProjectData.getActiveId(); } catch(e){}
updateProjectDisplay();
// Persist for the home page (green / "Review") and for the WP Creator tab,
@@ -753,10 +780,12 @@ function completeSOP(){
track('sop_generated', {woTypes: sop.woTypes.length, constraints: sop.constraints.length});
alert('✓ SOP Configuration Complete!\n\nSwitch to "Work Package Creation" to start creating Work Packages.');
// Hand the SOP to the embedded Work Package Creator and unlock its tab.
// Hand the SOP to the embedded Work Package Creator and unlock its tab (in case
// the user stays), then return to the project home page per the requested flow.
if(typeof onSOPReady === 'function') onSOPReady(sop);
alert('✓ SOP Configuration Complete!\n\nReturning to the project home page.');
window.location.href = 'index.html';
}
// ── COMMENTS ──────────────────────────────────────────────────────────────────

View File

@@ -136,7 +136,12 @@ function editQuality(id){
}
function renderCtxBar(){
const bar=document.getElementById('ctx-bar');
if(!SOP){ bar.innerHTML=`<div class="ctx-empty">No SOP loaded — <button class="link-btn" onclick="loadSampleSOP()">load the sample</button> or import one from the Configuration tool.</div>`; return; }
if(!SOP){
bar.innerHTML = activeProjectId
? `<div class="ctx-empty">No SOP found for this project yet — complete the <strong>SOP Configuration</strong> first, then return here.</div>`
: `<div class="ctx-empty">No SOP loaded — <button class="link-btn" onclick="loadSampleSOP()">load the sample</button> or import one from the Configuration tool.</div>`;
return;
}
const p=SOP.project||{}, g=SOP.governance||{};
const sample=SOP.meta&&SOP.meta.sample?`<span class="ctx-sample">SAMPLE</span>`:'';
bar.innerHTML=`<div class="ctx-main"><div class="ctx-proj">${esc(p.name||'Untitled')} ${sample}</div>
@@ -1039,7 +1044,11 @@ loadStore();
if(d && d.woTypes){ SOP = d; applySOP(); newPackage(); return; }
}
} catch(e){}
loadSampleSOP();
// No SOP found. Only show the Micron SAMPLE for a standalone preview (no project
// context). For a real project, never substitute sample data — show the empty
// state so it's clear the project's SOP must be completed first.
if(activeProjectId){ SOP=null; renderCtxBar(); newPackage(); }
else { loadSampleSOP(); }
})();
setRadio('status','Draft');
renderSavedList();