// ── STATE ──────────────────────────────────────────────────────────────────── let currentStep = 1; let state = { project: {name:'', number:'', client:'', division:'', site:''}, team: {pm:'', cm:'', qm:''}, signoffRoles: [{role:'Superintendent',name:''},{role:'Foreman',name:''}], wpTypes: [], governance: {woformat:'', wosize:'', issuance:[]}, quality: {qcreq:'', photo:'', hold:''}, platforms: {tracking:'CxAlloy', commissioning:'CxAlloy'}, sequence: [], constraints: [], sources: [] }; // ── LIBRARIES ──────────────────────────────────────────────────────────────── const DISCIPLINES = [ 'Conduit Installation', 'Cable Tray Installation', 'Instrument Raceway Installation', 'Wire / Cable Pull', 'Electrical/Instrument Cable Installation', 'Cable Terminations', 'Electrical Junction Box Installation', 'Instrument Installation', 'Control Panel Installation', 'Heat Tracing Installation' ]; const DEFAULT_WP_TYPES = [ {name:'Rough-In', enabled:true}, {name:'Mechanical Install', enabled:true}, {name:'Panel Install', enabled:true}, {name:'Conduit Install', enabled:true}, {name:'Tray Install', enabled:true}, {name:'Mechanical Tubing', enabled:false}, {name:'Instrument Install', enabled:true}, {name:'Wire Pull', enabled:true}, {name:'Terminations', enabled:true}, {name:'Prefab/Kitting', enabled:false}, {name:'Network Cabling', enabled:false}, {name:'Fiber', enabled:false}, {name:'Calibrations', enabled:false} ]; const TYPE_DISCIPLINE_MAP = { 'Rough-In':'Conduit Installation', 'Mechanical Install':'Heat Tracing Installation', 'Panel Install':'Control Panel Installation', 'Conduit Install':'Conduit Installation', 'Tray Install':'Cable Tray Installation', 'Mechanical Tubing':'Heat Tracing Installation', 'Instrument Install':'Instrument Installation', 'Wire Pull':'Wire / Cable Pull', 'Terminations':'Cable Terminations', 'Prefab/Kitting':'Electrical Junction Box Installation', 'Network Cabling':'Cable Tray Installation', 'Fiber':'Wire / Cable Pull', 'Calibrations':'Instrument Installation' }; const STANDARD_10_CONSTRAINTS = [ {name:'Safety & Permitting', description:'Permits, safety reviews, environmental clearances'}, {name:'Quality Control / Inspection', description:'QC approval, inspection readiness'}, {name:'IFC Drawings & Specs', description:'Issued-for-Construction drawings and specifications'}, {name:'Schedule', description:'Scheduled sequence timing confirmed'}, {name:'Materials (on site, bagged & tagged)', description:'All required materials on site and staged'}, {name:'Prefabrication', description:'Prefabricated assemblies complete'}, {name:'Work Access & Laydown', description:'Work area accessible and staged'}, {name:'Craft Availability', description:'Required craft trades available'}, {name:'Construction Equipment & Tools', description:'Special equipment and tools on site'}, {name:'Scaffolding / Access Equipment', description:'Temporary access structures in place'} ]; const CONSTRAINT_LIBRARY = [ 'Utility Clearances', 'Third-Party Approvals', 'Engineering Change Notices', 'Commissioning Sign-Off', 'As-Built Documentation', 'Labeling & Identification', 'Testing & Certification', 'Rework Completion', 'Coordination with Other Trades', 'Environmental Controls', 'Critical Path Gate', 'Client Walkthrough Approval' ]; const OPTIONAL_ROLES = [ 'General Foreman', 'Construction Manager', 'HSE Professional', 'Quality Representative', 'Planner', 'Safety Manager', 'Project Controls Manager' ]; const LABOR_COST_CODES = [ '1000|Project Management', '2000|Design and Development', '2100|Design', '2110|Control System Design', '2120|Instrument Design', '2130|Electrical Design', '2140|Panel Design', '2141|Panel Design Rework', '2150|BIM', '2151|BIM Rework', '2160|Documentation', '2200|Development', '2210|PLC Programming', '2220|OIT Programming', '2230|SCADA Programming', '2240|Simulation Development', '2300|Customer Training', '3000|Operational Technology', '3100|OT Design', '3200|Rack Assembly', '3300|Network Configuration', '3400|Computer Configuration', '4000|Construction', '4010|Instruments Install', '4020|Network & Computers Install', '4040|PLC Install', '4050|Panel Install', '4060|Electrical Install', '4070|Mechanical Install', '4080|Security Install', '4090|Radio Install', '4100|Commissioning', '6000|Production', '7000|Quality', '7100|Panel Quality Control', '7200|Factory Acceptance Testing', '7300|Site Acceptance Testing', '8000|Safety', '9000|Administration' ]; // ── INITIALIZATION ──────────────────────────────────────────────────────────── window.addEventListener('DOMContentLoaded',()=>{ initializeWPTypes(); renderOptionalRoles(); renderStandardConstraints(); renderSequenceSteps(); renderSources(); updateStepUI(); }); function initializeWPTypes(){ state.wpTypes = JSON.parse(JSON.stringify(DEFAULT_WP_TYPES)).map(t=>({...t,discipline:TYPE_DISCIPLINE_MAP[t.name]||''})); renderWPTypes(); } // ── RENDERING ───────────────────────────────────────────────────────────────── function renderWPTypes(){ const container = document.getElementById('wp-types-table'); container.innerHTML = `
Type
Discipline
`; state.wpTypes.forEach((t,i)=>{ const row = document.createElement('div'); row.className = 'wp-type-row'; row.innerHTML = `
${t.name}
`; container.appendChild(row); }); } function toggleWPType(i){ state.wpTypes[i].enabled = !state.wpTypes[i].enabled; renderWPTypes(); } function renderOptionalRoles(){ const container = document.getElementById('optional-roles-list'); const current = state.signoffRoles.filter(r=>r.role!=='Superintendent'&&r.role!=='Foreman'); container.innerHTML = current.map((r,i)=>`
`).join(''); } function addOptionalRole(){ state.signoffRoles.push({role:'General Foreman',name:''}); renderOptionalRoles(); } function removeRole(i){ state.signoffRoles.splice(i,1); renderOptionalRoles(); } function renderStandardConstraints(){ const container = document.getElementById('standard-constraints'); container.innerHTML = STANDARD_10_CONSTRAINTS.map(c=>`
${c.description}
`).join(''); // Initialize constraints with all standard 10 state.constraints = STANDARD_10_CONSTRAINTS.map(c=>({...c})); } function toggleConstraint(name){ const idx = state.constraints.findIndex(c=>c.name===name); if(idx>=0) state.constraints.splice(idx,1); else state.constraints.push(STANDARD_10_CONSTRAINTS.find(c=>c.name===name)); } function showConstraintLibrary(){ const modal = document.getElementById('constraint-modal'); const lib = document.getElementById('constraint-library'); lib.innerHTML = CONSTRAINT_LIBRARY.map(c=>`
${c}
Click to add to this project
`).join(''); modal.style.display = 'flex'; } function closeConstraintModal(){ document.getElementById('constraint-modal').style.display = 'none'; } function addCustomConstraint(name){ if(!state.constraints.find(c=>c.name===name)){ state.constraints.push({name,description:''}); } closeConstraintModal(); renderStandardConstraints(); } function renderSequenceSteps(){ const container = document.getElementById('sequence-list'); if(!state.sequence.length) state.sequence = [{label:'Layout',kind:'step'},{label:'Installation',kind:'step'},{label:'Testing',kind:'step'},{label:'Commissioning',kind:'step'}]; container.innerHTML = state.sequence.map((s,i)=>`
`).join(''); } function addSequenceStep(){ state.sequence.push({label:'',kind:'step'}); renderSequenceSteps(); } function renderSources(){ const container = document.getElementById('sources-list'); if(!state.sources.length) state.sources = [ {label:'Design Drawings',system:'Procore',notes:'',link:''}, {label:'Specifications',system:'Procore',notes:'',link:''}, {label:'IO List',system:'controls.dev',notes:'',link:''}, {label:'Cable Schedule',system:'SharePoint',notes:'',link:''} ]; container.innerHTML = state.sources.map((s,i)=>`
`).join(''); } function addSource(){ state.sources.push({label:'',system:'',notes:'',link:''}); renderSources(); } // ── STEP NAVIGATION ─────────────────────────────────────────────────────────── function goToStep(n){ if(!validateStep(currentStep)) return; currentStep = n; updateStepUI(); } function nextStep(){ if(!validateStep(currentStep)) return; if(currentStep < 10){ currentStep++; updateStepUI(); }else{ exportSOP(); } } function previousStep(){ if(currentStep > 1){ currentStep--; updateStepUI(); } } function updateStepUI(){ // Hide all steps document.querySelectorAll('.step').forEach(s=>s.style.display='none'); document.getElementById(`step-${currentStep}`).style.display='block'; // Update step indicators document.querySelectorAll('.step-item').forEach(s=>s.classList.remove('active')); document.querySelector(`[data-step="${currentStep}"]`).classList.add('active'); // Update counter document.getElementById('current-step').textContent = currentStep; // Update buttons document.getElementById('prev-btn').disabled = currentStep === 1; document.getElementById('next-btn').style.display = currentStep < 10 ? 'block' : 'none'; document.getElementById('export-btn').style.display = currentStep === 10 ? 'block' : 'none'; // Collect form data on display collectStepData(); } function collectStepData(){ switch(currentStep){ case 1: state.project.name = document.getElementById('proj_name').value; state.project.number = document.getElementById('proj_number').value; state.project.client = document.getElementById('proj_client').value; state.project.division = document.getElementById('proj_division').value; state.project.site = document.getElementById('proj_site').value; break; case 2: state.team.pm = document.getElementById('proj_pm').value; state.team.cm = document.getElementById('proj_cm').value; state.team.qm = document.getElementById('proj_qm').value; break; case 3: state.signoffRoles[0].name = document.getElementById('role_super_name').value; state.signoffRoles[1].name = document.getElementById('role_foreman_name').value; break; case 5: state.governance.woformat = document.getElementById('gov_woformat').value; state.governance.wosize = document.getElementById('gov_wosize').value; const sel = document.getElementById('gov_issuance'); state.governance.issuance = Array.from(sel.selectedOptions).map(o=>o.value); break; case 6: state.quality.qcreq = document.getElementById('qual_qcreq').value; state.quality.photo = document.getElementById('qual_photo').value; state.quality.hold = document.getElementById('qual_hold').value; break; case 7: state.platforms.tracking = document.getElementById('plat_tracking').value; state.platforms.commissioning = document.getElementById('plat_commissioning').value; break; } } function validateStep(n){ collectStepData(); const proj = state.project; const team = state.team; if(n===1 && (!proj.name || !proj.number || !proj.client || !proj.division || !proj.site)){ alert('Please complete all required fields: Project Name, Number, Client, Division, and Site Location.'); return false; } if(n===5 && !state.governance.woformat){ alert('Please enter a Work Package Number Format.'); return false; } if(n===6 && !state.quality.qcreq){ alert('Please select a QC requirement.'); return false; } return true; } // ── EXPORT ──────────────────────────────────────────────────────────────────── function exportSOP(){ collectStepData(); const sop = { meta: {tool:'Work Package Configuration', sample:false}, project: { name: state.project.name, number: state.project.number, client: state.project.client, division: state.project.division, pm: state.team.pm, cm: state.team.cm, qm: state.team.qm, site: state.project.site }, roles: state.signoffRoles.filter(r=>r.role), governance: { issuance: state.governance.issuance.length ? state.governance.issuance : ['By Sector / Area'], woSize: state.governance.wosize, woFormat: state.governance.woformat }, woTypes: state.wpTypes.filter(t=>t.enabled).map(t=>({ name: t.name, enabled: true, discipline: t.discipline || '' })), sources: state.sources.filter(s=>s.label), field: {trackPlatform: state.platforms.tracking}, commissioning: {tool: state.platforms.commissioning}, quality: { qcReq: state.quality.qcreq, photo: state.quality.photo, holdPoints: state.quality.hold, cxTool: state.platforms.commissioning }, sequence: state.sequence.filter(s=>s.label).map(s=>({ label: s.label, kind: s.kind || 'step' })), costCodes: LABOR_COST_CODES, constraints: state.constraints.map(c=>({name: c.name, description: c.description || ''})) }; // Validate SOP if(!sop.project.name || !sop.project.number || !sop.governance.woFormat){ alert('Cannot export: Missing critical fields (Project Name, Number, or WP Format).'); return; } // Download as JSON const json = JSON.stringify(sop, null, 2); const blob = new Blob([json], {type: 'application/json'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `wp-sop-${sop.project.name.replace(/\s+/g,'-').toLowerCase()}.json`; document.body.appendChild(a); a.click(); a.remove(); alert(`✓ SOP exported: ${a.download}`); }