Add SOP config tool (HTML, JS, CSS)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
458
sop-config-app.js
Normal file
458
sop-config-app.js
Normal file
@@ -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 = `<div style="padding:0.5rem; font-size:12px; font-weight:600; display:grid; grid-template-columns:30px 200px 200px; gap:1rem; margin-bottom:1rem; border-bottom:1px solid var(--border);">
|
||||||
|
<div></div><div>Type</div><div>Discipline</div>
|
||||||
|
</div>`;
|
||||||
|
state.wpTypes.forEach((t,i)=>{
|
||||||
|
const row = document.createElement('div');
|
||||||
|
row.className = 'wp-type-row';
|
||||||
|
row.innerHTML = `
|
||||||
|
<input type="checkbox" ${t.enabled?'checked':''} onchange="toggleWPType(${i})">
|
||||||
|
<div>${t.name}</div>
|
||||||
|
<select onchange="state.wpTypes[${i}].discipline=this.value">
|
||||||
|
${DISCIPLINES.map(d=>`<option ${t.discipline===d?'selected':''}>${d}</option>`).join('')}
|
||||||
|
</select>
|
||||||
|
`;
|
||||||
|
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)=>`
|
||||||
|
<div style="display:grid; grid-template-columns:1fr 200px 30px; gap:1rem; align-items:center; padding:0.75rem; background:var(--bg); border-radius:6px; margin-bottom:0.5rem; border:1px solid var(--border);">
|
||||||
|
<select onchange="state.signoffRoles[${state.signoffRoles.indexOf(r)}].role=this.value">
|
||||||
|
${OPTIONAL_ROLES.map(o=>`<option ${r.role===o?'selected':''}>${o}</option>`).join('')}
|
||||||
|
</select>
|
||||||
|
<input type="text" placeholder="Name (optional)" value="${r.name||''}" onchange="state.signoffRoles[${state.signoffRoles.indexOf(r)}].name=this.value">
|
||||||
|
<button class="row-del" onclick="removeRole(${state.signoffRoles.indexOf(r)})">✕</button>
|
||||||
|
</div>
|
||||||
|
`).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=>`
|
||||||
|
<div class="constraint-check">
|
||||||
|
<input type="checkbox" id="const_${c.name}" checked onchange="toggleConstraint('${c.name}')">
|
||||||
|
<div style="flex:1;">
|
||||||
|
<label for="const_${c.name}" style="margin:0; font-weight:600;">${c.name}</label>
|
||||||
|
<div style="font-size:12px; color:var(--text-dim); margin-top:0.25rem;">${c.description}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).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=>`
|
||||||
|
<div class="constraint-option" onclick="addCustomConstraint('${c}')">
|
||||||
|
<strong>${c}</strong>
|
||||||
|
<div style="font-size:12px; color:var(--text-light); margin-top:0.25rem;">Click to add to this project</div>
|
||||||
|
</div>
|
||||||
|
`).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)=>`
|
||||||
|
<div class="sequence-row">
|
||||||
|
<input type="text" value="${s.label}" placeholder="Step name" onchange="state.sequence[${i}].label=this.value">
|
||||||
|
<select onchange="state.sequence[${i}].kind=this.value">
|
||||||
|
<option ${s.kind==='step'?'selected':''}>step</option>
|
||||||
|
<option ${s.kind==='gate'?'selected':''}>gate</option>
|
||||||
|
</select>
|
||||||
|
<button class="row-del" onclick="state.sequence.splice(${i},1); renderSequenceSteps()">✕</button>
|
||||||
|
</div>
|
||||||
|
`).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)=>`
|
||||||
|
<div class="source-row">
|
||||||
|
<input type="text" value="${s.label}" placeholder="Label" onchange="state.sources[${i}].label=this.value">
|
||||||
|
<input type="text" value="${s.system}" placeholder="System" onchange="state.sources[${i}].system=this.value">
|
||||||
|
<input type="text" value="${s.link}" placeholder="URL" onchange="state.sources[${i}].link=this.value">
|
||||||
|
<input type="text" value="${s.notes}" placeholder="Notes" onchange="state.sources[${i}].notes=this.value">
|
||||||
|
<button class="row-del" onclick="state.sources.splice(${i},1); renderSources()">✕</button>
|
||||||
|
</div>
|
||||||
|
`).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}`);
|
||||||
|
}
|
||||||
404
sop-config-styles.css
Normal file
404
sop-config-styles.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
277
sop-config-tool.html
Normal file
277
sop-config-tool.html
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Work Package SOP Configuration Tool</title>
|
||||||
|
<link rel="stylesheet" href="sop-config-styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<!-- HEADER -->
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-title">Work Package SOP Configuration</div>
|
||||||
|
<div class="header-subtitle">Generate project-specific baseline parameters for Work Package creation</div>
|
||||||
|
<div class="header-meta"><span class="step-counter"><span id="current-step">1</span> / 10</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- STEP INDICATORS -->
|
||||||
|
<div class="step-nav">
|
||||||
|
<div class="steps-container">
|
||||||
|
<div class="step-item active" data-step="1" onclick="goToStep(1)">Project</div>
|
||||||
|
<div class="step-item" data-step="2" onclick="goToStep(2)">Team</div>
|
||||||
|
<div class="step-item" data-step="3" onclick="goToStep(3)">Sign-Offs</div>
|
||||||
|
<div class="step-item" data-step="4" onclick="goToStep(4)">WP Types</div>
|
||||||
|
<div class="step-item" data-step="5" onclick="goToStep(5)">Governance</div>
|
||||||
|
<div class="step-item" data-step="6" onclick="goToStep(6)">Quality</div>
|
||||||
|
<div class="step-item" data-step="7" onclick="goToStep(7)">Platforms</div>
|
||||||
|
<div class="step-item" data-step="8" onclick="goToStep(8)">Sequence</div>
|
||||||
|
<div class="step-item" data-step="9" onclick="goToStep(9)">Constraints</div>
|
||||||
|
<div class="step-item" data-step="10" onclick="goToStep(10)">Sources</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- STEP CONTENT -->
|
||||||
|
<div class="step-content">
|
||||||
|
|
||||||
|
<!-- STEP 1: PROJECT BASICS -->
|
||||||
|
<div class="step" id="step-1" style="display: block;">
|
||||||
|
<h2>1. Project Basics</h2>
|
||||||
|
<div class="notice">Define the core project information that will be inherited by all Work Packages.</div>
|
||||||
|
<div class="field-grid">
|
||||||
|
<div class="field">
|
||||||
|
<label>Project Name *</label>
|
||||||
|
<input type="text" id="proj_name" placeholder="e.g., Micron — INC Construction Work Packages">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Project Number *</label>
|
||||||
|
<input type="text" id="proj_number" placeholder="e.g., 26-67-008">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Client Name *</label>
|
||||||
|
<input type="text" id="proj_client" placeholder="e.g., Micron Technology, Inc.">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Division / Sector *</label>
|
||||||
|
<input type="text" id="proj_division" placeholder="e.g., Semiconductor, Oil & Gas, Data Center">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Site Location *</label>
|
||||||
|
<input type="text" id="proj_site" placeholder="e.g., Boise, ID — Fab 7">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- STEP 2: PROJECT TEAM -->
|
||||||
|
<div class="step" id="step-2" style="display: none;">
|
||||||
|
<h2>2. Project Team Leadership</h2>
|
||||||
|
<div class="notice">Name the key project leaders. These are informational and will appear in SOP exports.</div>
|
||||||
|
<div class="field-grid">
|
||||||
|
<div class="field">
|
||||||
|
<label>Project Manager (PM)</label>
|
||||||
|
<input type="text" id="proj_pm" placeholder="e.g., Nick Siegfried">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Construction Manager (CM)</label>
|
||||||
|
<input type="text" id="proj_cm" placeholder="e.g., K. Boyd">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Quality Manager (QM)</label>
|
||||||
|
<input type="text" id="proj_qm" placeholder="e.g., D. Nguyen">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- STEP 3: SIGN-OFF ROLES -->
|
||||||
|
<div class="step" id="step-3" style="display: none;">
|
||||||
|
<h2>3. Required Sign-Off Roles</h2>
|
||||||
|
<div class="notice">Superintendent and Foreman are required. Add other roles as needed for your project structure.</div>
|
||||||
|
|
||||||
|
<div class="required-roles">
|
||||||
|
<div class="role-required">
|
||||||
|
<div class="role-checkbox">
|
||||||
|
<input type="checkbox" id="role_super" checked disabled>
|
||||||
|
<label>Superintendent *</label>
|
||||||
|
</div>
|
||||||
|
<input type="text" id="role_super_name" placeholder="Name (optional)" style="flex: 1; margin-left: 1rem;">
|
||||||
|
</div>
|
||||||
|
<div class="role-required">
|
||||||
|
<div class="role-checkbox">
|
||||||
|
<input type="checkbox" id="role_foreman" checked disabled>
|
||||||
|
<label>Foreman *</label>
|
||||||
|
</div>
|
||||||
|
<input type="text" id="role_foreman_name" placeholder="Name (optional)" style="flex: 1; margin-left: 1rem;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top: 2rem; border-top: 1px solid var(--border); padding-top: 1.5rem;">
|
||||||
|
<div class="sub-heading">Optional Additional Roles</div>
|
||||||
|
<div id="optional-roles-list" style="margin-top: 1rem;">
|
||||||
|
<!-- Added dynamically -->
|
||||||
|
</div>
|
||||||
|
<button class="add-btn" onclick="addOptionalRole()">+ Add Role</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- STEP 4: WORK PACKAGE TYPES -->
|
||||||
|
<div class="step" id="step-4" style="display: none;">
|
||||||
|
<h2>4. Work Package Types & Disciplines</h2>
|
||||||
|
<div class="notice">Enable the WP types your project will use. Assign a discipline to each for IWP constraint checklists.</div>
|
||||||
|
<div id="wp-types-table" style="margin-top: 1.5rem;">
|
||||||
|
<!-- Built dynamically -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- STEP 5: GOVERNANCE -->
|
||||||
|
<div class="step" id="step-5" style="display: none;">
|
||||||
|
<h2>5. Governance & WP Numbering</h2>
|
||||||
|
<div class="notice">Define how Work Packages are formatted, sized, and issued on this project.</div>
|
||||||
|
<div class="field-grid">
|
||||||
|
<div class="field">
|
||||||
|
<label>Work Package Number Format *</label>
|
||||||
|
<input type="text" id="gov_woformat" placeholder="e.g., WP##-[Sector]-[Discipline]-[TYPE]">
|
||||||
|
<small>Use ## for counter, [Sector] [Discipline] [TYPE] as variables</small>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Typical WP Size</label>
|
||||||
|
<input type="text" id="gov_wosize" placeholder="e.g., 3–5 days or 40–80 hours">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Issuance Strategy</label>
|
||||||
|
<select id="gov_issuance" multiple size="3">
|
||||||
|
<option selected>By Sector / Area</option>
|
||||||
|
<option>By Discipline</option>
|
||||||
|
<option>By Phase / Sequence</option>
|
||||||
|
<option>By Resource Availability</option>
|
||||||
|
</select>
|
||||||
|
<small>Hold Ctrl to select multiple</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- STEP 6: QUALITY -->
|
||||||
|
<div class="step" id="step-6" style="display: none;">
|
||||||
|
<h2>6. Quality & Inspection Strategy</h2>
|
||||||
|
<div class="notice">Establish project-wide quality expectations that cascade to every Work Package.</div>
|
||||||
|
<div class="field-grid col1">
|
||||||
|
<div class="field">
|
||||||
|
<label>QC Required? *</label>
|
||||||
|
<select id="qual_qcreq">
|
||||||
|
<option>Yes</option>
|
||||||
|
<option>Yes — Detailed inspection items</option>
|
||||||
|
<option>Yes — Sample / Spot-check</option>
|
||||||
|
<option>No</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Photo / Documentation Standard</label>
|
||||||
|
<select id="qual_photo">
|
||||||
|
<option selected>Key checkpoints only</option>
|
||||||
|
<option>Every step documented</option>
|
||||||
|
<option>As-built final condition only</option>
|
||||||
|
<option>None</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Hold Points & Witness Requirements</label>
|
||||||
|
<textarea id="qual_hold" rows="3" placeholder="e.g., HOLD: Prime QAQC to inspect rough-in before cover/cover-up. WITNESS: client QC to observe megger test before energization."></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- STEP 7: PLATFORMS -->
|
||||||
|
<div class="step" id="step-7" style="display: none;">
|
||||||
|
<h2>7. Tracking & Commissioning Platforms</h2>
|
||||||
|
<div class="notice">Select the tools used for construction tracking and commissioning. These can be the same or different systems.</div>
|
||||||
|
<div class="field-grid">
|
||||||
|
<div class="field">
|
||||||
|
<label>Construction Tracking Platform *</label>
|
||||||
|
<select id="plat_tracking">
|
||||||
|
<option>CxAlloy</option>
|
||||||
|
<option>Procore</option>
|
||||||
|
<option>Autodesk Construction Cloud (ACC)</option>
|
||||||
|
<option>Other</option>
|
||||||
|
</select>
|
||||||
|
<small>Primary tool for WP status, progress, and reporting</small>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Commissioning Tool *</label>
|
||||||
|
<select id="plat_commissioning">
|
||||||
|
<option selected>CxAlloy</option>
|
||||||
|
<option>Procore</option>
|
||||||
|
<option>Autodesk Construction Cloud (ACC)</option>
|
||||||
|
<option>Other</option>
|
||||||
|
</select>
|
||||||
|
<small>Tool for system turnover, testing, and sign-offs</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- STEP 8: SEQUENCE -->
|
||||||
|
<div class="step" id="step-8" style="display: none;">
|
||||||
|
<h2>8. Construction Sequence</h2>
|
||||||
|
<div class="notice">Define the major phases and logical sequence of construction work. These become predecessors in WP planning.</div>
|
||||||
|
<div id="sequence-list" style="margin-top: 1rem;">
|
||||||
|
<!-- Built dynamically -->
|
||||||
|
</div>
|
||||||
|
<button class="add-btn" onclick="addSequenceStep()">+ Add Sequence Step</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- STEP 9: CONSTRAINTS -->
|
||||||
|
<div class="step" id="step-9" style="display: none;">
|
||||||
|
<h2>9. Release Gate Constraints</h2>
|
||||||
|
<div class="notice">Constraints are readiness items that must be cleared before a WP can be issued. Start with the standard 10 (AWP Vol II), or add custom constraints per your project needs.</div>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 2rem;">
|
||||||
|
<div class="sub-heading">Standard AWP Constraints (Vol II §2.3.2)</div>
|
||||||
|
<div id="standard-constraints" style="margin-top: 1rem;">
|
||||||
|
<!-- Built dynamically -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="border-top: 1px solid var(--border); padding-top: 1.5rem;">
|
||||||
|
<div class="sub-heading">Custom Constraints (Optional)</div>
|
||||||
|
<div id="custom-constraints-list" style="margin-top: 1rem;">
|
||||||
|
<!-- Built dynamically -->
|
||||||
|
</div>
|
||||||
|
<button class="add-btn" onclick="showConstraintLibrary()">+ Add Custom Constraint</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- STEP 10: SOURCES & REFERENCES -->
|
||||||
|
<div class="step" id="step-10" style="display: none;">
|
||||||
|
<h2>10. Engineering Sources & References</h2>
|
||||||
|
<div class="notice">Link to key documents and systems that WP authors will reference (Procore drawings, IO lists, cable schedules, specifications, etc.).</div>
|
||||||
|
<div id="sources-list" style="margin-top: 1rem;">
|
||||||
|
<!-- Built dynamically -->
|
||||||
|
</div>
|
||||||
|
<button class="add-btn" onclick="addSource()">+ Add Source</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- NAVIGATION -->
|
||||||
|
<div class="step-navigation">
|
||||||
|
<button class="nav-btn" id="prev-btn" onclick="previousStep()">← Back</button>
|
||||||
|
<button class="nav-btn" id="next-btn" onclick="nextStep()">Next →</button>
|
||||||
|
<button class="nav-btn primary" id="export-btn" onclick="exportSOP()" style="display: none;">⤓ Export SOP JSON</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CONSTRAINT LIBRARY MODAL -->
|
||||||
|
<div id="constraint-modal" class="modal" style="display: none;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Add Custom Constraint</h3>
|
||||||
|
<button class="modal-close" onclick="closeConstraintModal()">✕</button>
|
||||||
|
</div>
|
||||||
|
<div id="constraint-library" style="max-height: 400px; overflow-y: auto; margin: 1rem 0;">
|
||||||
|
<!-- Built dynamically -->
|
||||||
|
</div>
|
||||||
|
<button class="nav-btn" onclick="closeConstraintModal()">Done</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="sop-config-app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user