commit 50a8da90f39646293dffe893c8e3a6c530875d40 Author: n.siegfried Date: Thu Jun 11 09:57:43 2026 -0700 Add SOP config tool (HTML, JS, CSS) Co-Authored-By: Claude Opus 4.8 (1M context) diff --git a/sop-config-app.js b/sop-config-app.js new file mode 100644 index 0000000..b1ac6f7 --- /dev/null +++ b/sop-config-app.js @@ -0,0 +1,458 @@ +// ── 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}`); +} diff --git a/sop-config-styles.css b/sop-config-styles.css new file mode 100644 index 0000000..edacd2e --- /dev/null +++ b/sop-config-styles.css @@ -0,0 +1,404 @@ +:root { + --primary: #2563eb; + --primary-light: #dbeafe; + --success: #16a34a; + --warning: #ea580c; + --danger: #dc2626; + --text: #1f2937; + --text-light: #6b7280; + --text-dim: #9ca3af; + --border: #e5e7eb; + --bg: #f9fafb; + --bg-card: #ffffff; + --shadow: 0 1px 3px rgba(0,0,0,0.1); + --shadow-lg: 0 10px 25px rgba(0,0,0,0.1); +} + +* { margin: 0; padding: 0; box-sizing: border-box; } +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif; + color: var(--text); + background: var(--bg); + line-height: 1.5; +} + +.container { + max-width: 900px; + margin: 0 auto; + padding: 2rem 1rem; +} + +/* HEADER */ +.header { + background: var(--bg-card); + border-bottom: 2px solid var(--primary); + padding: 2rem; + margin-bottom: 2rem; + border-radius: 8px; + box-shadow: var(--shadow); +} +.header-title { font-size: 28px; font-weight: 700; color: var(--text); margin-bottom: 0.5rem; } +.header-subtitle { font-size: 14px; color: var(--text-light); margin-bottom: 1rem; } +.header-meta { font-size: 12px; color: var(--text-dim); } +.step-counter { + background: var(--primary-light); + color: var(--primary); + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-weight: 600; +} + +/* STEP NAVIGATION */ +.step-nav { + margin-bottom: 2rem; + overflow-x: auto; +} +.steps-container { + display: flex; + gap: 0.5rem; + min-width: min-content; + padding: 0.5rem; +} +.step-item { + padding: 0.75rem 1rem; + border-radius: 6px; + background: var(--bg); + border: 2px solid var(--border); + cursor: pointer; + font-size: 12px; + font-weight: 600; + white-space: nowrap; + transition: all 0.2s; +} +.step-item:hover { background: var(--primary-light); border-color: var(--primary); } +.step-item.active { + background: var(--primary); + color: white; + border-color: var(--primary); +} + +/* STEP CONTENT */ +.step-content { + background: var(--bg-card); + padding: 2rem; + border-radius: 8px; + box-shadow: var(--shadow); + margin-bottom: 2rem; +} +.step { display: none; } +.step h2 { + font-size: 20px; + font-weight: 700; + margin-bottom: 0.5rem; + color: var(--text); +} +.notice { + font-size: 13px; + color: var(--text-light); + background: var(--primary-light); + padding: 0.75rem 1rem; + border-radius: 6px; + margin-bottom: 1.5rem; + border-left: 4px solid var(--primary); +} + +/* FIELDS */ +.field-grid { + display: grid; + gap: 1.5rem; +} +.field-grid.col1 { grid-template-columns: 1fr; } +.field { + display: flex; + flex-direction: column; +} +.field label { + font-size: 13px; + font-weight: 600; + margin-bottom: 0.5rem; + color: var(--text); +} +.field input, +.field select, +.field textarea { + padding: 0.75rem; + border: 1px solid var(--border); + border-radius: 6px; + font-size: 14px; + font-family: inherit; + color: var(--text); + background: var(--bg); + transition: border-color 0.2s; +} +.field input:focus, +.field select:focus, +.field textarea:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 3px var(--primary-light); +} +.field small { + font-size: 12px; + color: var(--text-dim); + margin-top: 0.25rem; +} + +/* SUB-HEADING */ +.sub-heading { + font-size: 14px; + font-weight: 700; + color: var(--text); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +/* REQUIRED ROLES */ +.required-roles { + display: flex; + flex-direction: column; + gap: 1rem; +} +.role-required { + display: flex; + align-items: center; + padding: 1rem; + background: var(--bg); + border-radius: 6px; + border: 1px solid var(--border); +} +.role-checkbox { + display: flex; + align-items: center; + gap: 0.5rem; +} +.role-checkbox input[type="checkbox"] { + width: 18px; + height: 18px; + cursor: pointer; +} +.role-checkbox label { + margin: 0; + font-weight: 600; + cursor: pointer; +} + +/* WP TYPES TABLE */ +#wp-types-table { + overflow-x: auto; +} +.wp-type-row { + display: grid; + grid-template-columns: 30px 200px 200px 100px; + gap: 1rem; + align-items: center; + padding: 1rem; + background: var(--bg); + border-radius: 6px; + margin-bottom: 0.5rem; + border: 1px solid var(--border); +} +.wp-type-row input[type="checkbox"] { + width: 18px; + height: 18px; + cursor: pointer; +} + +/* SEQUENCE */ +.sequence-row { + display: grid; + grid-template-columns: 200px 100px 30px; + gap: 1rem; + align-items: center; + padding: 1rem; + background: var(--bg); + border-radius: 6px; + margin-bottom: 0.5rem; + border: 1px solid var(--border); +} +.sequence-row .row-del { + width: 30px; + height: 30px; + padding: 0; + background: var(--danger); + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + font-weight: 600; +} +.sequence-row .row-del:hover { background: #b91c1c; } + +/* CONSTRAINTS */ +.constraint-check { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem; + background: var(--bg); + border-radius: 6px; + border: 1px solid var(--border); + margin-bottom: 0.5rem; +} +.constraint-check input[type="checkbox"] { + width: 18px; + height: 18px; + cursor: pointer; +} +.constraint-check label { + margin: 0; + cursor: pointer; + font-size: 14px; +} + +/* SOURCES */ +.source-row { + display: grid; + grid-template-columns: 150px 150px 250px 150px 30px; + gap: 1rem; + align-items: center; + padding: 1rem; + background: var(--bg); + border-radius: 6px; + margin-bottom: 0.5rem; + border: 1px solid var(--border); +} +.source-row input, +.source-row select { + padding: 0.5rem; + font-size: 12px; + border: 1px solid var(--border); + border-radius: 4px; +} +.source-row .row-del { + width: 30px; + height: 30px; + padding: 0; + background: var(--danger); + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + font-weight: 600; +} +.source-row .row-del:hover { background: #b91c1c; } + +/* BUTTONS */ +.add-btn { + padding: 0.75rem 1.25rem; + background: var(--primary); + color: white; + border: none; + border-radius: 6px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: background 0.2s; +} +.add-btn:hover { background: #1d4ed8; } + +.nav-btn { + padding: 0.75rem 1.5rem; + background: var(--bg); + border: 2px solid var(--border); + border-radius: 6px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s; +} +.nav-btn:hover { + border-color: var(--primary); + color: var(--primary); +} +.nav-btn.primary { + background: var(--primary); + color: white; + border-color: var(--primary); +} +.nav-btn.primary:hover { + background: #1d4ed8; + border-color: #1d4ed8; +} +.nav-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* NAVIGATION */ +.step-navigation { + display: flex; + gap: 1rem; + justify-content: space-between; + padding: 1.5rem; + background: var(--bg-card); + border-radius: 8px; + box-shadow: var(--shadow); +} + +/* MODAL */ +.modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0,0,0,0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} +.modal-content { + background: var(--bg-card); + border-radius: 8px; + padding: 2rem; + max-width: 600px; + max-height: 80vh; + overflow-y: auto; + box-shadow: var(--shadow-lg); + position: relative; +} +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; + border-bottom: 1px solid var(--border); + padding-bottom: 1rem; +} +.modal-header h3 { font-size: 18px; margin: 0; } +.modal-close { + background: none; + border: none; + font-size: 20px; + cursor: pointer; + color: var(--text-dim); +} +.modal-close:hover { color: var(--text); } + +.constraint-option { + padding: 0.75rem; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + margin-bottom: 0.5rem; + cursor: pointer; + transition: all 0.2s; +} +.constraint-option:hover { + border-color: var(--primary); + background: var(--primary-light); +} + +/* RESPONSIVE */ +@media (max-width: 768px) { + .wp-type-row, + .source-row { + grid-template-columns: 1fr; + } + .step-navigation { + flex-direction: column; + } +} diff --git a/sop-config-tool.html b/sop-config-tool.html new file mode 100644 index 0000000..ebf7bcf --- /dev/null +++ b/sop-config-tool.html @@ -0,0 +1,277 @@ + + + + + + Work Package SOP Configuration Tool + + + +
+ +
+
Work Package SOP Configuration
+
Generate project-specific baseline parameters for Work Package creation
+
1 / 10
+
+ + +
+
+
Project
+
Team
+
Sign-Offs
+
WP Types
+
Governance
+
Quality
+
Platforms
+
Sequence
+
Constraints
+
Sources
+
+
+ + +
+ + +
+

1. Project Basics

+
Define the core project information that will be inherited by all Work Packages.
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + +
+
+ + + + + + +