From c583daae3060a388e011d9c5f02b0a665746fe0a Mon Sep 17 00:00:00 2001 From: "n.siegfried" Date: Thu, 11 Jun 2026 12:10:03 -0700 Subject: [PATCH] Initial commit: WP Suite project files Co-Authored-By: Claude Opus 4.8 --- Project-SDE-WP-Suite.code-workspace | 8 + index.html | 501 +++++++++++++++++++++ prime-controls-logo.jpg | Bin 0 -> 29314 bytes sop-config-app.js | 587 ++++++++++++++++++++++++ sop-config-styles.css | 507 +++++++++++++++++++++ sop-config-tool.html | 309 +++++++++++++ theme-light.css | 159 +++++++ work-package-suite-app.js | 569 +++++++++++++++++++++++ work-package-suite-styles.css | 462 +++++++++++++++++++ work-package-suite.html | 322 +++++++++++++ wp-creation-app.js | 676 ++++++++++++++++++++++++++++ wp-creation-index.html | 235 ++++++++++ wp-creation-styles.css | 559 +++++++++++++++++++++++ 13 files changed, 4894 insertions(+) create mode 100644 Project-SDE-WP-Suite.code-workspace create mode 100644 index.html create mode 100644 prime-controls-logo.jpg create mode 100644 sop-config-app.js create mode 100644 sop-config-styles.css create mode 100644 sop-config-tool.html create mode 100644 theme-light.css create mode 100644 work-package-suite-app.js create mode 100644 work-package-suite-styles.css create mode 100644 work-package-suite.html create mode 100644 wp-creation-app.js create mode 100644 wp-creation-index.html create mode 100644 wp-creation-styles.css diff --git a/Project-SDE-WP-Suite.code-workspace b/Project-SDE-WP-Suite.code-workspace new file mode 100644 index 0000000..876a149 --- /dev/null +++ b/Project-SDE-WP-Suite.code-workspace @@ -0,0 +1,8 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": {} +} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..4506d17 --- /dev/null +++ b/index.html @@ -0,0 +1,501 @@ + + + + + + Work Package Suite — Prime Controls + + + + + +
+ +
+ + +
+ + +
+

Work Package Suite

+

Standardized approach to Work Package creation for Prime Controls construction projects. Configure project parameters, define constraints, and generate compliant work packages.

+
+ + + + + +
+

Getting Started

+
    +
  1. Click "Unified Suite" to begin
  2. +
  3. Complete the 10-step SOP Configuration for your project (~15 minutes)
  4. +
  5. Switch to "Work Package Creation" tab to start authoring Work Packages
  6. +
  7. Leave feedback on any page using the feedback button below
  8. +
+
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+

About This Suite

+
+
+

Three Complementary Tools

+

Unified workflow for end-to-end SOP+WP creation, standalone SOP generator, and individual WP authoring. Choose the approach that fits your workflow.

+
+
+

Leave Feedback

+

Use the feedback section on this page or within any tool. All comments are stored locally and can be exported for team review and iteration.

+
+
+

Offline & Collaborative

+

All tools work entirely in your browser. Export SOP and Work Package data as JSON for sharing, version control, and integration.

+
+
+
+ +
+ + +
+

Work Package Suite v1.0 | Prime Controls | All files work offline with local browser storage

+
+ + + + diff --git a/prime-controls-logo.jpg b/prime-controls-logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d5cb0b883307b426133c4d573240b9c28f82f009 GIT binary patch literal 29314 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 + 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(); +} + +// ── LOAD SAMPLE DATA ────────────────────────────────────────────────────────── +function loadSampleData(){ + // Populate Step 1 + document.getElementById('proj_name').value = 'Micron — INC Construction Work Packages'; + document.getElementById('proj_number').value = '26-67-008'; + document.getElementById('proj_client').value = 'Micron Technology, Inc.'; + document.getElementById('proj_division').value = 'Semiconductor'; + document.getElementById('proj_site').value = 'Boise, ID — Fab 7'; + + // Populate Step 2 + document.getElementById('proj_pm').value = 'Nick Siegfried'; + document.getElementById('proj_cm').value = 'K. Boyd'; + document.getElementById('proj_qm').value = 'D. Nguyen'; + + // Step 3 already has defaults + document.getElementById('role_super_name').value = 'John Smith'; + document.getElementById('role_foreman_name').value = 'Mike Jones'; + + // Populate Step 5 + document.getElementById('gov_woformat').value = 'WP##-[Sector]-[Discipline]-[TYPE]'; + document.getElementById('gov_wosize').value = '3–5 days / 40–80 hours'; + + // Populate Step 6 + document.getElementById('qual_qcreq').value = 'Yes — Detailed inspection items'; + document.getElementById('qual_photo').value = 'Key checkpoints only'; + document.getElementById('qual_hold').value = 'HOLD: Prime QAQC to inspect rough-in before cover/cover-up.\nWITNESS: Client QC to observe megger test before energization.'; + + // Step 7 already has defaults + + // Collect all data + collectStepData(); + + alert('✓ Sample data loaded!\n\nNavigate through the SOP steps to see example values. You can edit or replace any field.'); + + // Switch to step 1 + currentStep = 1; + updateStepUI(); +} + +// ── 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}`); + + // Also offer to export comments + if(allComments.length > 0){ + setTimeout(()=>{ + if(confirm('Export feedback comments as well? (CSV file)')){ + exportComments(); + } + }, 500); + } +} + +// ── COMMENTS ────────────────────────────────────────────────────────────────── +function toggleComments(){ + const panel = document.getElementById('comments-panel'); + panel.style.display = panel.style.display === 'none' ? 'block' : 'none'; + if(panel.style.display === 'block'){ + loadStepComments(); + } +} + +function submitComment(){ + const name = document.getElementById('commenter-name').value || 'Anonymous'; + const text = document.getElementById('comment-text').value.trim(); + + if(!text){ + alert('Please enter a comment.'); + return; + } + + const comment = { + step: currentStep, + name: name, + text: text, + timestamp: new Date().toLocaleString() + }; + + allComments.push(comment); + localStorage.setItem('sop_config_comments', JSON.stringify(allComments)); + + document.getElementById('comment-text').value = ''; + document.getElementById('commenter-name').value = ''; + loadStepComments(); + alert('✓ Comment submitted! Thank you for your feedback.'); +} + +function loadStepComments(){ + const saved = localStorage.getItem('sop_config_comments'); + if(saved) allComments = JSON.parse(saved); + + const stepComments = allComments.filter(c=>c.step===currentStep); + const list = document.getElementById('comments-list'); + + if(stepComments.length === 0){ + list.innerHTML = '
No comments yet on this step.
'; + }else{ + list.innerHTML = stepComments.map(c=>` +
+
${c.name} • ${c.timestamp}
+
${c.text.replace(//g,'>')}
+
+ `).join(''); + } +} + +function exportComments(){ + const saved = localStorage.getItem('sop_config_comments'); + if(!saved || JSON.parse(saved).length === 0){ + alert('No comments to export.'); + return; + } + + allComments = JSON.parse(saved); + + // Build CSV + let csv = 'Step,Name,Timestamp,Feedback\n'; + allComments.forEach(c=>{ + const text = `"${c.text.replace(/"/g,'""')}"`; + csv += `${c.step},"${c.name}","${c.timestamp}",${text}\n`; + }); + + const blob = new Blob([csv], {type: 'text/csv'}); + const a = document.createElement('a'); + a.href = URL.createObjectURL(blob); + a.download = `sop-config-feedback-${new Date().toISOString().split('T')[0]}.csv`; + document.body.appendChild(a); + a.click(); + a.remove(); + + alert(`✓ ${allComments.length} comments exported to CSV.`); +} diff --git a/sop-config-styles.css b/sop-config-styles.css new file mode 100644 index 0000000..57d3b9b --- /dev/null +++ b/sop-config-styles.css @@ -0,0 +1,507 @@ +: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: linear-gradient(135deg, var(--primary) 0%, #1e40af 100%); + color: white; + padding: 1.5rem 2rem; + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: var(--shadow-lg); + margin-bottom: 2rem; + border-radius: 0; +} + +.header-left { + display: flex; + align-items: center; + gap: 1.5rem; + flex: 1; +} + +.logo { + display: flex; + align-items: center; + gap: 0.5rem; + text-decoration: none; + color: white; + font-weight: 700; + font-size: 14px; + transition: opacity 0.2s; +} + +.logo:hover { opacity: 0.9; } + +.logo-icon { + width: 36px; + height: 36px; + background: rgba(255,255,255,0.2); + border-radius: 6px; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + font-weight: 700; +} + +.logo-text { + display: flex; + flex-direction: column; + line-height: 1; + font-size: 12px; +} + +.header-title { + font-size: 20px; + font-weight: 700; + margin-bottom: 0.25rem; + color: white; +} + +.header-subtitle { + font-size: 12px; + opacity: 0.85; + margin-bottom: 0; + color: white; +} + +.header-right { + display: flex; + align-items: center; + gap: 1rem; +} + +.header-button { + padding: 0.5rem 1rem; + background: rgba(255,255,255,0.2); + color: white; + border: 1px solid rgba(255,255,255,0.4); + border-radius: 6px; + cursor: pointer; + font-size: 13px; + font-weight: 600; + transition: all 0.2s; +} + +.header-button:hover { + background: rgba(255,255,255,0.3); + border-color: white; +} + +.header-meta { font-size: 12px; color: var(--text-dim); } +.step-counter { + background: rgba(255,255,255,0.2); + color: white; + padding: 0.4rem 0.8rem; + border-radius: 20px; + font-weight: 600; + font-size: 11px; +} + +/* 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; + } +} + +/* COMMENTS SECTION */ +.comments-section { + margin-top: 2rem; + padding-top: 1.5rem; + border-top: 2px solid var(--border); +} + +.comments-toggle { + padding: 0.5rem 1rem; + background: var(--primary-light); + color: var(--primary); + border: 1px solid var(--primary); + border-radius: 6px; + font-size: 13px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s; +} + +.comments-toggle:hover { + background: var(--primary); + color: white; +} diff --git a/sop-config-tool.html b/sop-config-tool.html new file mode 100644 index 0000000..de11143 --- /dev/null +++ b/sop-config-tool.html @@ -0,0 +1,309 @@ + + + + + + Work Package SOP Configuration Tool + + + + +
+ +
+
+ +
+
SOP Configuration Tool
+
Project-specific baseline parameters
+
+
+
+ + 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.
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+ + +
+ + + +
+
+ + + + + + + diff --git a/theme-light.css b/theme-light.css new file mode 100644 index 0000000..23ebf28 --- /dev/null +++ b/theme-light.css @@ -0,0 +1,159 @@ +/* IBM Carbon Design System - Light Theme (g10) */ +:root { + --cds-interactive-01: #0f62fe; + --cds-interactive-02: #393939; + --cds-interactive-03: #0f62fe; + --cds-interactive-04: #0f62fe; + --cds-ui-background: #f4f4f4; + --cds-ui-01: #ffffff; + --cds-ui-02: #f4f4f4; + --cds-ui-03: #e0e0e0; + --cds-ui-04: #8d8d8d; + --cds-ui-05: #161616; + --cds-text-01: #161616; + --cds-text-02: #525252; + --cds-text-03: #a8a8a8; + --cds-text-04: #ffffff; + --cds-text-05: #6f6f6f; + --cds-text-error: #da1e28; + --cds-icon-01: #161616; + --cds-icon-02: #525252; + --cds-icon-03: #ffffff; + --cds-link-01: #0f62fe; + --cds-link-02: #0043ce; + --cds-inverse-link: #78a9ff; + --cds-field-01: #ffffff; + --cds-field-02: #f4f4f4; + --cds-inverse-01: #ffffff; + --cds-inverse-02: #393939; + --cds-support-01: #da1e28; + --cds-support-02: #198038; + --cds-support-03: #f1c21b; + --cds-support-04: #0043ce; + --cds-inverse-support-01: #fa4d56; + --cds-inverse-support-02: #42be65; + --cds-inverse-support-03: #f1c21b; + --cds-inverse-support-04: #4589ff; + --cds-overlay: rgba(22, 22, 22, .5); + --cds-danger: #da1e28; + --cds-focus: #0f62fe; + --cds-hover-primary: #0353e9; + --cds-active-primary: #002d9c; + --cds-hover-primary-text: #0043ce; + --cds-hover-secondary: #4c4c4c; + --cds-active-secondary: #6f6f6f; + --cds-hover-tertiary: #0353e9; + --cds-active-tertiary: #002d9c; + --cds-hover-ui: #e5e5e5; + --cds-hover-light-ui: #e5e5e5; + --cds-hover-selected-ui: #cacaca; + --cds-active-ui: #c6c6c6; + --cds-active-light-ui: #cacaca; + --cds-selected-ui: #e5e5e5; + --cds-selected-light-ui: #e5e5e5; + --cds-inverse-hover-ui: #353535; + --cds-hover-danger: #ba121b; + --cds-active-danger: #750e13; + --cds-hover-row: #f4f4f4; + --cds-visited-link: #0043ce; + --cds-disabled-01: #f4f4f4; + --cds-disabled-02: #bdbdbd; + --cds-disabled-03: #8d8d8d; + --cds-button-primary: #0f62fe; + --cds-button-secondary: #393939; + --cds-button-tertiary: #0f62fe; + --cds-button-danger-primary: #da1e28; + --cds-button-danger-secondary: #da1e28; + --cds-background: #f4f4f4; + --cds-background-inverse: #161616; + --cds-background-brand: #0f62fe; + --cds-background-active: #e5e5e5; + --cds-background-hover: #e8e8e8; + --cds-background-selected: #e5e5e5; + --cds-background-selected-hover: #cacaca; + --cds-layer: #ffffff; + --cds-layer-accent: #f4f4f4; + --cds-layer-accent-hover: #e8e8e8; + --cds-layer-accent-active: #e0e0e0; + --cds-layer-hover: #e8e8e8; + --cds-layer-active: #e5e5e5; + --cds-layer-selected: #e5e5e5; + --cds-layer-selected-hover: #cacaca; + --cds-layer-selected-inverse: #393939; + --cds-field: #ffffff; + --cds-field-hover: #e8e8e8; + --cds-border-subtle: #e0e0e0; + --cds-border-strong: #8d8d8d; + --cds-border-inverse: #393939; + --cds-border-interactive: #0f62fe; + --cds-border-subtle-selected: #c6c6c6; + --cds-text-primary: #161616; + --cds-text-secondary: #525252; + --cds-text-placeholder: #a8a8a8; + --cds-text-helper: #6f6f6f; + --cds-text-on-color: #ffffff; + --cds-text-inverse: #f4f4f4; + --cds-text-disabled: #bdbdbd; + --cds-link-primary: #0f62fe; + --cds-link-secondary: #0043ce; + --cds-link-visited: #0043ce; + --cds-link-inverse: #78a9ff; + --cds-link-primary-hover: #0353e9; + --cds-icon-primary: #161616; + --cds-icon-secondary: #525252; + --cds-icon-on-color: #ffffff; + --cds-icon-inverse: #f4f4f4; + --cds-icon-disabled: #bdbdbd; + --cds-support-error: #da1e28; + --cds-support-success: #198038; + --cds-support-warning: #f1c21b; + --cds-support-info: #0043ce; + --cds-support-error-inverse: #fa4d56; + --cds-support-success-inverse: #42be65; + --cds-support-warning-inverse: #f1c21b; + --cds-support-info-inverse: #4589ff; + --cds-interactive: #0f62fe; + --cds-shadow: rgba(0, 0, 0, .16); +} + +/* Typography */ +body { + font-family: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif; + color: var(--cds-text-primary); + background: var(--cds-background); + font-size: 1rem; + font-weight: 400; + line-height: 1.5; +} + +h1, h2, h3, h4, h5, h6 { + font-weight: 600; + line-height: 1.28572; +} + +h1 { font-size: 2rem; } +h2 { font-size: 1.75rem; } +h3 { font-size: 1.25rem; } +h4 { font-size: 1rem; } +h5 { font-size: .875rem; } +h6 { font-size: .75rem; } + +a { + color: var(--cds-link-primary); + text-decoration: none; +} + +a:hover { + color: var(--cds-link-primary-hover); + text-decoration: underline; +} + +button { + font-family: inherit; + cursor: pointer; +} + +input, textarea, select { + font-family: inherit; + color: var(--cds-text-primary); +} diff --git a/work-package-suite-app.js b/work-package-suite-app.js new file mode 100644 index 0000000..636b2d3 --- /dev/null +++ b/work-package-suite-app.js @@ -0,0 +1,569 @@ +// ── GLOBAL STATE ────────────────────────────────────────────────────────────── +let currentTool = 'sop'; +let currentStep = 1; +let sopComplete = false; +let allComments = []; + +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: [] +}; + +let sop = null; // Generated SOP for WP tool + +// ── 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(); + updateProjectDisplay(); +}); + +function initializeWPTypes(){ + state.wpTypes = JSON.parse(JSON.stringify(DEFAULT_WP_TYPES)).map(t=>({...t,discipline:TYPE_DISCIPLINE_MAP[t.name]||''})); + renderWPTypes(); +} + +// ── LOAD SAMPLE DATA ────────────────────────────────────────────────────────── +function loadSampleData(){ + // Populate Step 1 + document.getElementById('proj_name').value = 'Micron — INC Construction Work Packages'; + document.getElementById('proj_number').value = '26-67-008'; + document.getElementById('proj_client').value = 'Micron Technology, Inc.'; + document.getElementById('proj_division').value = 'Semiconductor'; + document.getElementById('proj_site').value = 'Boise, ID — Fab 7'; + + // Populate Step 2 + document.getElementById('proj_pm').value = 'Nick Siegfried'; + document.getElementById('proj_cm').value = 'K. Boyd'; + document.getElementById('proj_qm').value = 'D. Nguyen'; + + // Step 3 already has defaults + document.getElementById('role_super_name').value = 'John Smith'; + document.getElementById('role_foreman_name').value = 'Mike Jones'; + + // Populate Step 5 + document.getElementById('gov_woformat').value = 'WP##-[Sector]-[Discipline]-[TYPE]'; + document.getElementById('gov_wosize').value = '3–5 days / 40–80 hours'; + + // Populate Step 6 + document.getElementById('qual_qcreq').value = 'Yes — Detailed inspection items'; + document.getElementById('qual_photo').value = 'Key checkpoints only'; + document.getElementById('qual_hold').value = 'HOLD: Prime QAQC to inspect rough-in before cover/cover-up.\nWITNESS: Client QC to observe megger test before energization.'; + + // Step 7 already has defaults + + // Collect all data + collectStepData(); + + alert('✓ Sample data loaded!\n\nNavigate through the SOP steps to see example values. You can edit or replace any field.'); + + // Switch to step 1 + currentStep = 1; + updateStepUI(); + updateProjectDisplay(); +} + +// ── TOOL SWITCHING ──────────────────────────────────────────────────────────── +function switchTool(tool){ + currentTool = tool; + + // Update nav tabs + document.querySelectorAll('.nav-tab').forEach(t=>t.classList.remove('active')); + document.querySelector(`[data-tab="${tool}"]`).classList.add('active'); + + // Update content + document.querySelectorAll('.tool').forEach(t=>t.classList.remove('active')); + document.getElementById(`tool-${tool}`).classList.add('active'); + + // Reset step counter + if(tool === 'sop'){ + document.getElementById('total-steps').textContent = '10'; + }else{ + document.getElementById('total-steps').textContent = '—'; + } + + updateStepUI(); + updateProjectDisplay(); +} + +function updateProjectDisplay(){ + const projName = document.getElementById('proj_name')?.value || 'Project'; + const display = document.getElementById('project-display'); + if(display) display.textContent = sopComplete ? `✓ ${projName} (SOP Ready)` : projName; +} + +// ── RENDERING (SOP) ──────────────────────────────────────────────────────────── +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(''); + 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(); + } +} + +function previousStep(){ + if(currentStep > 1){ + currentStep--; + updateStepUI(); + } +} + +function updateStepUI(){ + // SOP steps + document.querySelectorAll('[id^="sop-step-"]').forEach(s=>s.style.display='none'); + document.getElementById(`sop-step-${currentStep}`)?.style.display && (document.getElementById(`sop-step-${currentStep}`).style.display='block'); + + // 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('sop-prev-btn').disabled = currentStep === 1; + document.getElementById('sop-next-btn').style.display = currentStep < 10 ? 'block' : 'none'; + document.getElementById('sop-complete-btn').style.display = currentStep === 10 ? 'block' : 'none'; + + // Collect form data + 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; + + 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; +} + +// ── SOP COMPLETION ──────────────────────────────────────────────────────────── +function completeSOP(){ + if(!validateStep(10)) return; + collectStepData(); + + 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 || ''})) + }; + + sopComplete = true; + updateProjectDisplay(); + + alert('✓ SOP Configuration Complete!\n\nSwitch to "Work Package Creation" to start creating Work Packages.'); + + // Update WP tool status + document.getElementById('sop-status').textContent = '✓ Ready'; + document.getElementById('sop-progress-text').textContent = `Project: ${sop.project.name} | ${sop.woTypes.length} WP Types | ${sop.constraints.length} Constraints`; +} + +// ── COMMENTS ────────────────────────────────────────────────────────────────── +function toggleComments(){ + const panel = document.getElementById('comments-panel'); + panel.style.display = panel.style.display === 'none' ? 'block' : 'none'; + if(panel.style.display === 'block') loadStepComments(); +} + +function submitComment(){ + const name = document.getElementById('commenter-name').value || 'Anonymous'; + const text = document.getElementById('comment-text').value.trim(); + + if(!text){ alert('Please enter a comment.'); return; } + + const comment = { + step: currentStep, + name: name, + text: text, + timestamp: new Date().toLocaleString() + }; + + allComments.push(comment); + localStorage.setItem('wp_suite_comments', JSON.stringify(allComments)); + + document.getElementById('comment-text').value = ''; + document.getElementById('commenter-name').value = ''; + loadStepComments(); + alert('✓ Comment submitted!'); +} + +function loadStepComments(){ + const saved = localStorage.getItem('wp_suite_comments'); + if(saved) allComments = JSON.parse(saved); + + const stepComments = allComments.filter(c=>c.step===currentStep); + const list = document.getElementById('comments-list'); + + if(stepComments.length === 0){ + list.innerHTML = '
No comments yet on this step.
'; + }else{ + list.innerHTML = stepComments.map(c=>` +
+
${c.name} • ${c.timestamp}
+
${c.text.replace(//g,'>')}
+
+ `).join(''); + } +} diff --git a/work-package-suite-styles.css b/work-package-suite-styles.css new file mode 100644 index 0000000..3d233bc --- /dev/null +++ b/work-package-suite-styles.css @@ -0,0 +1,462 @@ +: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; +} + +.app-container { + display: flex; + flex-direction: column; + min-height: 100vh; +} + +/* HEADER */ +.header { + background: linear-gradient(135deg, var(--primary) 0%, #1e40af 100%); + color: white; + padding: 1.5rem 2rem; + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: var(--shadow-lg); +} + +.header-left { + flex: 1; + display: flex; + align-items: center; + gap: 1.5rem; +} + +.logo { + display: flex; + align-items: center; + gap: 0.5rem; + text-decoration: none; + color: white; + font-weight: 700; + font-size: 14px; + transition: opacity 0.2s; +} + +.logo:hover { opacity: 0.9; } + +.logo-icon { + width: 36px; + height: 36px; + background: rgba(255,255,255,0.2); + border-radius: 6px; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + font-weight: 700; +} + +.logo-text { + display: flex; + flex-direction: column; + line-height: 1; + font-size: 12px; +} + +.header-title { + font-size: 24px; + font-weight: 700; + margin-bottom: 0; +} + +.header-subtitle { + font-size: 13px; + opacity: 0.9; + min-height: 20px; +} + +.header-right { + display: flex; + align-items: center; + gap: 1.5rem; +} + +.header-button { + padding: 0.5rem 1rem; + background: rgba(255,255,255,0.2); + color: white; + border: 1px solid rgba(255,255,255,0.4); + border-radius: 6px; + cursor: pointer; + font-size: 13px; + font-weight: 600; + transition: all 0.2s; +} + +.header-button:hover { + background: rgba(255,255,255,0.3); + border-color: white; +} + +.step-counter { + background: rgba(255,255,255,0.2); + padding: 0.4rem 0.8rem; + border-radius: 20px; + font-size: 12px; + font-weight: 600; +} + +/* MAIN NAVIGATION */ +.main-nav { + display: flex; + gap: 0.5rem; + padding: 1rem 2rem; + background: var(--bg-card); + border-bottom: 1px solid var(--border); + box-shadow: var(--shadow); +} + +.nav-tab { + padding: 0.75rem 1.5rem; + background: var(--bg); + border: 2px solid var(--border); + border-radius: 6px; + cursor: pointer; + font-size: 14px; + font-weight: 600; + display: flex; + align-items: center; + gap: 0.5rem; + transition: all 0.2s; +} + +.nav-tab:hover { + border-color: var(--primary); + color: var(--primary); +} + +.nav-tab.active { + background: var(--primary); + color: white; + border-color: var(--primary); +} + +.tab-icon { font-size: 16px; } + +/* CONTENT AREA */ +.content-area { + flex: 1; + padding: 2rem; + max-width: 1000px; + margin: 0 auto; + width: 100%; +} + +.tool { + display: none; +} + +.tool.active { + display: block; +} + +/* STEP NAV */ +.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; +} + +/* 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 */ +#wp-types-table { overflow-x: auto; } + +.wp-type-row { + display: grid; + grid-template-columns: 30px 200px 200px; + gap: 1rem; + align-items: center; + padding: 1rem; + background: var(--bg); + border-radius: 6px; + margin-bottom: 0.5rem; + border: 1px solid var(--border); +} + +/* 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(--success); + color: white; + border-color: var(--success); +} + +.nav-btn.primary:hover { background: #15803d; border-color: #15803d; } + +.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); +} + +/* COMMENTS */ +.comments-section { + margin-top: 2rem; + padding-top: 1.5rem; + border-top: 2px solid var(--border); +} + +.comments-toggle { + padding: 0.5rem 1rem; + background: var(--primary-light); + color: var(--primary); + border: 1px solid var(--primary); + border-radius: 6px; + font-size: 13px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s; +} + +.comments-toggle:hover { background: var(--primary); color: white; } + +/* 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); +} + +.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); } + +/* UTILITY */ +.sub-heading { + font-size: 14px; + font-weight: 700; + color: var(--text); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +/* RESPONSIVE */ +@media (max-width: 768px) { + .header { flex-direction: column; text-align: center; gap: 1rem; } + .main-nav { flex-wrap: wrap; } + .content-area { padding: 1rem; } + .step-content { padding: 1rem; } + .wp-type-row { grid-template-columns: 1fr; } + .step-navigation { flex-direction: column; } +} diff --git a/work-package-suite.html b/work-package-suite.html new file mode 100644 index 0000000..c630860 --- /dev/null +++ b/work-package-suite.html @@ -0,0 +1,322 @@ + + + + + + Work Package Suite + + + + +
+ +
+
+ +
Work Package Suite
+
+
+
+ + 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.
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+ + +
+ + + +
+ +
+ + + + +
+
+

📋 Work Package Creation

+

Complete the SOP Configuration first to enable Work Package creation.

+
+

SOP Status: Not started

+

+
+
+
+ +
+ +
+ + + + + + + diff --git a/wp-creation-app.js b/wp-creation-app.js new file mode 100644 index 0000000..b4c7413 --- /dev/null +++ b/wp-creation-app.js @@ -0,0 +1,676 @@ +/* Work Package (IWP) form — the downstream installation work package record. + Parameters (numbering, types, QC, sources) are inherited from the project SOP + exported by the Configuration tool. Implements the AWP constraint checklist + + release gate from IR 272-2 Vol II. */ + +// ── SAMPLE SOP (same shape the Configuration tool exports) ─────────────────── +const SAMPLE_SOP = { + meta:{tool:'Work Package Configuration', sample:true}, + project:{name:'Micron — INC Construction Work Packages', number:'26-67-008', client:'Micron Technology, Inc.', division:'Semiconductor', pm:'Nick Siegfried', cm:'K. Boyd', qm:'D. Nguyen', site:'Boise, ID — Fab'}, + roles:[{role:'General Foreman',name:'M. Torres'},{role:'Superintendent',name:'K. Boyd'},{role:'Safety Manager / Lead',name:'A. Reyes'},{role:'Quality Manager',name:'D. Nguyen'},{role:'Planner',name:'L. Graver'}], + governance:{ issuance:['By Sector / Area','By Discipline'], woSize:'3–5 days', woFormat:'WP##-[Sector]-[Discipline]-[TYPE]' }, + woTypes:[ + {name:'Conduit Install', enabled:true}, {name:'Tray Install', enabled:true}, + {name:'Wire Pull', enabled:true}, {name:'Terminations', enabled:true}, + {name:'Instrument Install', enabled:true}, {name:'Panel Install', enabled:true}, + ], + sources:[ + {label:'Design Drawings',system:'Procore',notes:'',link:'https://us02.procore.com/562949954073428/project/documents/folders/drawings'}, + {label:'Specifications',system:'Procore',notes:'',link:'https://us02.procore.com/562949954073428/project/documents/folders/specs'}, + {label:'IO List',system:'controls.dev',notes:'',link:'https://controls.dev/io'}, + {label:'Cable Schedule',system:'SharePoint',notes:'',link:'https://primecontrols.sharepoint.com/cable-schedule'}, + ], + field:{ trackPlatform:'CxAlloy' }, + quality:{ qcReq:'Yes', qcScope:'Detailed inspection items', photo:'Key checkpoints only', cxTool:'CxAlloy', holdPoints:'HOLD: Prime QAQC to inspect rough-in before cover/cover-up. WITNESS: client QC to observe megger / insulation-resistance test before energization.' }, + sequence:[{label:'Layout',kind:'step'},{label:'Conduit Install',kind:'step'},{label:'Tray Install',kind:'step'},{label:'Wire Pull',kind:'step'},{label:'Terminations',kind:'step'},{label:'Commissioning',kind:'step'}], + costCodes:['5100 - Rough In','5200 - Wire Pull','5300 - Terminations','5400 - Instrumentation'], +}; + +// AWP / COAA discipline checklists (Vol II, Appendix I) +// Limited to Prime's I&C scope of work (subset of the AWP Appendix I discipline checklists) +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']; +// Standard AWP constraint set (Vol I §2.3.2; Vol II IWP checklists) +const DEFAULT_CONSTRAINTS = ['Safety & Permitting','Quality Control / Inspection','IFC Drawings & Specs','Schedule','Materials (on site, bagged & tagged)','Prefabrication','Work Access & Laydown','Craft Availability','Construction Equipment & Tools','Scaffolding / Access Equipment']; +const SIGNOFF_ROLES = ['Planner','Superintendent','HSE Professional','Quality Representative','Work Foreman']; +const STATUS_ORDER = ['Draft','Scheduled','Issued','In Progress','QC','Closed']; +const ISSUED_IDX = STATUS_ORDER.indexOf('Issued'); + +// Acumatica cost codes (comment 10) — code|description +const 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','2290|Programming Subcontract','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','4940|Contract Labor','4960|Electrical Subcontract','4970|Mechanical Subcontract','4980|Security Subcontract','4990|Other/Radio Subcontract','5010|Instrument Material','5020|Network & Computers Material','5030|Software Material','5040|PLC Material','5050|Panel Material','5060|Electrical Material','5070|Mechanical Material','5080|Security Material','5090|Radio Material','6000|Production','7000|Quality','7100|Panel Quality Control','7200|Factory Acceptance Testing','7300|Site Acceptance Testing','8000|Safety','9000|Administration','9100|Warranty','9200|Freight','9300|Travel','9350|Jobsite Costs/Consumable/Other Direct Costs','9400|Contingency','9450|Other','9500|Accrued Incentive Compensation','9600|Sales Tax','9650|Job Cost Labor Burden','9700|Bonding','9800|Non-Billable Compensation']; +// Acumatica allowed units of measure (comment 15) — common first +const ACU_UNITS = ['EA','EACH','FT','M','METER','HR','DAYS','MINUTE','KG','LITER','CASE','LOT','LS','PK','PACK','PALLET','PIECE','BOTTLE','CAN']; +// WP type → AWP discipline (comment 6: discipline derived from type) +const TYPE_DISCIPLINE = {'Conduit Install':'Conduit Installation','Tray Install':'Cable Tray Installation','Wire Pull':'Wire / Cable Pull','Terminations':'Cable Terminations','Instrument Install':'Instrument Installation','Panel Install':'Control Panel Installation'}; +// WP type → short discipline/trade code used in the WP number (comment 2: single source = type) +const TYPE_TRADE = {'Conduit Install':'ELEC','Tray Install':'ELEC','Wire Pull':'ELEC','Terminations':'ELEC','Instrument Install':'INST','Panel Install':'ELEC'}; + +// Example built work package (comment 4 / "Load Example") — WP02 export +const EXAMPLE_PKG = {"number":"WP02-1P-ELEC-Conduit","status":"Issue","subject":"FAB 1P Horn Strobe Conduit","type":"Conduit Install","system":"Chilled Water","location":"FAB / LVL 1 / Sect P","cost":"4060","wbs":"2001","assignees":"David Velazquez (Catapult Solutions Group), Jesus Casiano-Figueroa (Prime Controls)","distribution":"Bill Clarida (Prime Controls), Sean Tolley (Prime Controls), Jefferson Dufriend (Prime Controls)","due":"2026-06-18","spec":"26_05_33_31 - Conduit","desc":"1P Conduit run for horns and strobes","work":"Layout conduit route\nUsing ladders and MEWP install conduit and boxes with proper strapping, supports, and pull points according to 2D drawings and 3D model. Reference package drawings included in attachments.\nWhen complete initiate inspection with Prime QAQC","workSteps":["Layout conduit route","Using ladders and MEWP install conduit and boxes with proper strapping, supports, and pull points according to 2D drawings and 3D model. Reference package drawings included in attachments.","When complete initiate inspection with Prime QAQC"],"numberDims":{"Sector":"1P","Discipline":"ELEC"},"hours":"60","seq":"Layout","materials":[{"qty":"400","unit":"FT","desc":"3/4\" EMT"},{"qty":"300","unit":"FT","desc":"1\" EMT"},{"qty":"50","unit":"FT","desc":"7/8\" strut"},{"qty":"50","unit":"FT","desc":"1-5/8\" strut"},{"qty":"8","unit":"EA","desc":"4x4x4 NEMA 3 Box"},{"qty":"2","unit":"EA","desc":"1\" C-Type Conduit Body"},{"qty":"1","unit":"EA","desc":"1\" T-Type Conduit Body"},{"qty":"2","unit":"EA","desc":"3/4\" C-Type Conduit Body"},{"qty":"2","unit":"EA","desc":"3/4\" T-Type Conduit Body"},{"qty":"3","unit":"EA","desc":"3/4\" EMT LB & Cover"},{"qty":"6","unit":"EA","desc":"Hoffman F44GCPNK Horn Strobe Box"},{"qty":"2","unit":"EA","desc":"EMO 2x4 Box"},{"qty":"4","unit":"EA","desc":"1\" Bond Bushing w/ Lug"},{"qty":"4","unit":"EA","desc":"3/4\" Bond Bushing w/ Lug"},{"qty":"50","unit":"EA","desc":"1/4\"x3\" Toggle Bolt"},{"qty":"100","unit":"EA","desc":"Drywall Anchor"},{"qty":"5","unit":"EA","desc":"1\" to 3/4\" threaded reducer"}],"attachments":[{"doc":"EE-1YA-2P-5_ HPM PANEL CALLOUT 05","rev":"0","link":"https://us02.procore.com/webclients/host/companies/562949953431440/projects/562949954073428/tools/document-viewer/prostore/562950644886790"}],"kitStatus":"Open","kitOwner":"Ian Spielburg","kitDate":"2026-06-15","mimoTime":"2026-06-11T10:30","mimoLoc":"04","constraints":[{"name":"Safety & Permitting","status":"cleared","comment":""},{"name":"Quality Control / Inspection","status":"cleared","comment":""},{"name":"IFC Drawings & Specs","status":"cleared","comment":""},{"name":"Schedule","status":"cleared","comment":""},{"name":"Materials (on site, bagged & tagged)","status":"cleared","comment":""},{"name":"Prefabrication","status":"cleared","comment":""},{"name":"Work Access & Laydown","status":"cleared","comment":""},{"name":"Craft Availability","status":"cleared","comment":""},{"name":"Construction Equipment & Tools","status":"open","comment":"Boom lift not yet on site"},{"name":"Scaffolding / Access Equipment","status":"cleared","comment":""}],"qc":"Yes — Detailed inspection items","photo":"Key checkpoints only","hold":"HOLD: Prime QAQC to inspect rough-in before cover/cover-up. WITNESS: client QC to observe megger / insulation-resistance test before energization.","overrides":{"wp_photo":"Change Order"},"signoffs":[{"role":"Planner","name":"L. Graver","date":"2026-06-10","signed":true,"dateReason":""},{"role":"Superintendent","name":"K. Boyd","date":"2026-06-10","signed":true,"dateReason":""},{"role":"HSE Professional","name":"A. Reyes","date":"","signed":false,"dateReason":""},{"role":"Quality Representative","name":"D. Nguyen","date":"","signed":false,"dateReason":""},{"role":"Work Foreman","name":"M. Torres","date":"","signed":false,"dateReason":""}],"holds":[],"actualHrs":"54","installedQty":"200 ft","redlines":"Conduit size changed. need to update model","lessons":"Prepping trapeze hangers with conduit straps saved time","project":"Micron — INC Construction Work Packages","track":"CxAlloy"}; + +// ── STATE ──────────────────────────────────────────────────────────────────── +let SOP=null, editingId=null, numberDirty=false; +let pkgMaterials=[], pkgAttach=[], pkgConstraints=[], pkgSignoffs=[], pkgHolds=[], pkgWorkSteps=[]; +let pkgOverrides={}; // {fieldId: reason} for SOP-locked fields that were edited +let numberDims={}; // {Sector:'', Discipline:''} dimensions that build the WP number (comment 3) +let prevStatus='Draft'; +let devMode=false; // comment 7: pauses usage tracking during review +let savedPackages=[]; +let currentView='Work Package Form'; + +// ── HELPERS ────────────────────────────────────────────────────────────────── +function gv(id){ return document.getElementById(id)?.value?.trim() || ''; } +function esc(v){ if(v==null) return ''; return String(v).replace(/&/g,'&').replace(//g,'>'); } +function ns(){ return ''; } +function cell(v){ return v ? esc(v) : ns(); } +function pad2(n){ return n<10?'0'+n:''+n; } +function typeCode(name){ return (name||'').replace(/[^a-z0-9]+/gi,''); } +function linkify(v){ if(!v) return ''; return esc(v).replace(/(https?:\/\/[^\s]+)/g,u=>`${u}`); } +function toast(msg){ let t=document.getElementById('toast'); if(!t){ t=document.createElement('div'); t.id='toast'; document.body.appendChild(t); } t.textContent=msg; t.classList.add('show'); clearTimeout(toast._t); toast._t=setTimeout(()=>t.classList.remove('show'),2200); } +function getRadio(name){ const s=document.querySelector(`.radio-pill.selected input[name="${name}"]`); return s?.closest('.radio-pill')?.dataset?.val||''; } +function setRadio(name,val){ document.querySelectorAll(`.radio-pill input[name="${name}"]`).forEach(i=>{const p=i.closest('.radio-pill'); const on=p.dataset.val===val; p.classList.toggle('selected',on); i.checked=on;}); } +function enabledTypes(){ return ((SOP&&SOP.woTypes)||[]).filter(t=>t.enabled!==false); } +function constraintNames(){ return (SOP&&Array.isArray(SOP.constraints)&&SOP.constraints.length)?SOP.constraints:DEFAULT_CONSTRAINTS; } +function nextSeq(){ return savedPackages.length+1; } + +// ── SOP LOADING ────────────────────────────────────────────────────────────── +function loadSampleSOP(){ SOP=JSON.parse(JSON.stringify(SAMPLE_SOP)); applySOP(); newPackage(); toast('Sample SOP loaded — ' + (SOP.project&&SOP.project.name||'project')); track('sample_loaded'); } +function importSOP(ev){ + const f=ev.target.files&&ev.target.files[0]; if(!f) return; const r=new FileReader(); + r.onload=()=>{ try{ const d=JSON.parse(r.result); if(!d.woTypes){ alert('That file does not look like an SOP export from the Configuration tool.'); return; } + SOP=d; applySOP(); newPackage(); toast('Loaded SOP — '+((d.project&&d.project.name)||'project')); track('sop_imported'); + }catch(e){ alert('Could not read that file.'); } ev.target.value=''; }; + r.readAsText(f); +} +function applySOP(){ + renderCtxBar(); buildTypePicker(); buildCostCodes(); buildSequencePicker(); buildNumberDims(); + renderSopRefLinks(); renderSpecFolderLink(); + // SOP-inherited Quality fields — populated then locked (editable only with a logged reason) + if(SOP.quality){ + document.getElementById('wp_qc').value=[SOP.quality.qcReq,SOP.quality.qcScope].filter(Boolean).join(' — '); + document.getElementById('wp_photo').value=SOP.quality.photo||''; + document.getElementById('wp_hold').value=SOP.quality.holdPoints||''; + } + lockQuality('wp_qc'); lockQuality('wp_photo'); lockQuality('wp_hold'); + const hint=document.getElementById('wp_number_hint'); hint.textContent = SOP.governance&&SOP.governance.woFormat ? 'auto-built · format: '+SOP.governance.woFormat : ''; + buildConstraints(); buildSignoffs(); + if(!pkgMaterials.length){ pkgMaterials=[{qty:'',unit:'',desc:''}]; buildMaterials(); } + if(!pkgAttach.length){ pkgAttach=[{doc:'',rev:'',link:''}]; buildAttach(); } + if(!pkgWorkSteps.length){ pkgWorkSteps=['']; buildWorkSteps(); } + updateNumber(); updateReleaseBanner(); +} +function buildCostCodes(){ + const sel=document.getElementById('wp_cost'); const cur=sel.value; + sel.innerHTML=``+COST_CODES.map(c=>{const [code,desc]=c.split('|'); return ``;}).join(''); + if(cur) sel.value=cur; +} +// ── SOP-LOCKED QUALITY FIELDS (comment 3) ──────────────────────────────────── +function sopValueFor(id){ + if(!SOP||!SOP.quality) return ''; + if(id==='wp_qc') return [SOP.quality.qcReq,SOP.quality.qcScope].filter(Boolean).join(' — '); + if(id==='wp_photo') return SOP.quality.photo||''; + if(id==='wp_hold') return SOP.quality.holdPoints||''; + return ''; +} +function lockQuality(id){ + const el=document.getElementById(id); if(!el) return; + const fromSOP = !!(SOP&&SOP.quality) && !pkgOverrides[id]; + el.readOnly = fromSOP; + el.classList.toggle('sop-inherited', fromSOP); // comment 8: blue-tinted SOP field + el.classList.toggle('locked-field', false); + const wrap=el.closest('.field'); const btn=wrap&&wrap.querySelector('.lock-edit'); const note=wrap&&wrap.querySelector('.override-note'); + if(btn) btn.textContent = fromSOP ? '🔒 Edit (reason required)' : '↺ Revert to SOP'; + if(note) note.innerHTML = pkgOverrides[id] ? `Overridden: ${esc(pkgOverrides[id])}` : ''; +} +function editQuality(id){ + if(pkgOverrides[id]){ // currently overridden -> revert to SOP value + delete pkgOverrides[id]; + document.getElementById(id).value=sopValueFor(id); + lockQuality(id); track('quality_reverted',{field:id}); return; + } + const reason=prompt('This field is set by the project SOP. Enter the reason for overriding it on this package:'); + if(reason===null) return; + if(!reason.trim()){ alert('A reason is required to override an SOP field.'); return; } + pkgOverrides[id]=reason.trim(); + const el=document.getElementById(id); el.readOnly=false; el.classList.remove('locked-field'); el.focus(); + lockQuality(id); track('quality_overridden',{field:id}); +} +function renderCtxBar(){ + const bar=document.getElementById('ctx-bar'); + if(!SOP){ bar.innerHTML=`
No SOP loaded — or import one from the Configuration tool.
`; return; } + const p=SOP.project||{}, g=SOP.governance||{}; + const sample=SOP.meta&&SOP.meta.sample?`SAMPLE`:''; + bar.innerHTML=`
${esc(p.name||'Untitled')} ${sample}
+
${esc(p.number||'')}${p.division?' · '+esc(p.division):''}
+
${enabledTypes().length} typesformat ${esc(g.woFormat||'—')}track: ${esc((SOP.field&&SOP.field.trackPlatform)||'—')}
`; +} + +// ── DEV MODE (comment 7) ───────────────────────────────────────────────────── +function toggleDevMode(){ + devMode=!devMode; + document.body.classList.toggle('dev-mode', devMode); + document.getElementById('dev-banner').style.display = devMode ? '' : 'none'; + toast(devMode ? 'DEV MODE on — usage tracking paused' : 'DEV MODE off — tracking resumed'); +} + +// ── SOP REFERENCE LINKS (comments 1, 3, 4) ─────────────────────────────────── +function sopLinkedSources(){ return ((SOP&&SOP.sources)||[]).filter(s=>s.link); } +function renderSopRefLinks(){ + const box=document.getElementById('sop-ref-links'); if(!box) return; + const srcs=sopLinkedSources(); + if(!srcs.length){ box.innerHTML=''; return; } + box.innerHTML=``+ + ``; +} +function renderSpecFolderLink(){ + const el=document.getElementById('spec-folder-link'); if(!el) return; + const spec=sopLinkedSources().find(s=>/spec/i.test(s.label)); + el.innerHTML = spec ? `↗ Open spec folder (${esc(spec.system||'SOP')})` : ''; +} +function buildTypePicker(){ document.getElementById('wp_type').innerHTML=``+enabledTypes().map(t=>``).join(''); } +function buildSequencePicker(){ const steps=((SOP&&SOP.sequence)||[]).filter(s=>s.kind!=='gate'&&(s.label||'').trim()); document.getElementById('wp_seq').innerHTML=``+steps.map(s=>``).join(''); } +function derivedDiscipline(){ const t=gv('wp_type'); return TYPE_DISCIPLINE[t]||''; } +function onTypeChange(){ + const d=derivedDiscipline(); const el=document.getElementById('wp_discipline_derived'); + if(el) el.textContent = d ? d : '—'; + updateNumber(); track('type_selected'); +} + +// ── WP NUMBER (auto-built from per-WP dimensions + type + sequence) ─────────── +function numberTokens(){ + const fmt=(SOP&&SOP.governance&&SOP.governance.woFormat)||'WP##-[TYPE]'; + return (fmt.match(/\[[^\]]+\]/g)||[]).map(t=>t.replace(/[\[\]]/g,'')).filter(t=>t.toUpperCase()!=='TYPE'); +} +function buildNumberDims(){ + const wrap=document.getElementById('number-dims'); if(!wrap) return; + const toks=numberTokens(); + wrap.innerHTML = toks.length ? toks.map(t=>{ + if(/^discipline$/i.test(t)){ + return `
+
${esc(numberDims[t]||'—')}
+
trade code from WP type — same discipline used for tracking
`; + } + return `
+
`; + }).join('') : '
SOP number format has no scope tokens.
'; +} +function typeNumberCode(name){ return (name||'').replace(/\b(install|installation)\b/ig,'').replace(/[^a-z0-9]+/gi,'').trim(); } +function updateNumber(){ + const fmt=(SOP&&SOP.governance&&SOP.governance.woFormat)||'WP##-[TYPE]'; + const t=gv('wp_type'); + let out=fmt.replace(/WO##|WP##/i,'WP'+pad2(editingSeq())); + numberTokens().forEach(tok=>{ + let v; + if(/^discipline$/i.test(tok)){ v=TYPE_TRADE[t]||''; numberDims[tok]=v; const b=document.getElementById('ndim_disc_box'); if(b) b.textContent=v||'—'; } + else v=(numberDims[tok]||'').trim(); + out=out.split('['+tok+']').join(v||('['+tok+']')); + }); + out=out.split('[TYPE]').join(t?typeNumberCode(t):'[TYPE]'); + document.getElementById('wp_number').value=out; +} +function editingSeq(){ // keep an existing package's sequence number if editing, else next + if(editingId){ const ix=savedPackages.findIndex(p=>p.id===editingId); if(ix>=0) return ix+1; } + return nextSeq(); +} + +// ── MATERIAL LIST ──────────────────────────────────────────────────────────── +function unitOptions(cur){ + const list=ACU_UNITS.slice(); const c=(cur||'').toUpperCase(); + if(c && !list.includes(c)) list.unshift(c); + return ``+list.map(u=>``).join(''); +} +function buildMaterials(){ + const tb=document.getElementById('material-body'); tb.innerHTML=''; + pkgMaterials.forEach((m,i)=>{ const tr=document.createElement('tr'); + tr.innerHTML=` + + + `; + tb.appendChild(tr); }); +} +function addMaterial(){ pkgMaterials.push({qty:'',unit:'',desc:''}); buildMaterials(); } +function removeMaterial(i){ pkgMaterials.splice(i,1); if(!pkgMaterials.length)pkgMaterials=[{qty:'',unit:'',desc:''}]; buildMaterials(); } +function downloadMaterialTemplate(){ + const csv='Qty,Unit,Description\n10,FT,Single deep strut gold\n4,EA,2-hole strut base gold\n200,FT,3/4 EMT conduit\n'; + const blob=new Blob([csv],{type:'text/csv'}); const a=document.createElement('a'); + a.href=URL.createObjectURL(blob); a.download='material-list-template.csv'; document.body.appendChild(a); a.click(); a.remove(); setTimeout(()=>URL.revokeObjectURL(a.href),1000); track('material_template'); +} +function parseCSV(text){ + const rows=[]; text.replace(/\r\n/g,'\n').split('\n').forEach(line=>{ + if(!line.trim()) return; + const cells=[]; let cur='', q=false; + for(let i=0;i{ + try{ + const rows=parseCSV(r.result); if(!rows.length){ alert('No rows found.'); return; } + let start=0; const h=rows[0].map(c=>c.trim().toLowerCase()); + const qi=h.indexOf('qty'), ui=h.indexOf('unit'), di=h.indexOf('description'); + let cq=0,cu=1,cd=2; + if(qi>-1||ui>-1||di>-1){ start=1; if(qi>-1)cq=qi; if(ui>-1)cu=ui; if(di>-1)cd=di; } + const imported=[]; + for(let i=start;i{ const row=document.createElement('div'); row.className='workstep-row'; + row.innerHTML=`${i+1} + + `; + tb.appendChild(row); }); +} +function addWorkStep(){ pkgWorkSteps.push(''); buildWorkSteps(); } +function removeWorkStep(i){ pkgWorkSteps.splice(i,1); if(!pkgWorkSteps.length)pkgWorkSteps=['']; buildWorkSteps(); } + +// ── ATTACHMENTS ────────────────────────────────────────────────────────────── +function buildAttach(){ + const tb=document.getElementById('attach-body'); tb.innerHTML=''; + pkgAttach.forEach((a,i)=>{ const tr=document.createElement('tr'); + tr.innerHTML=` + + + `; + tb.appendChild(tr); }); +} +function addAttach(){ pkgAttach.push({doc:'',rev:'',link:''}); buildAttach(); } +function removeAttach(i){ pkgAttach.splice(i,1); if(!pkgAttach.length)pkgAttach=[{doc:'',rev:'',link:''}]; buildAttach(); } + +// ── CONSTRAINTS + RELEASE GATE ─────────────────────────────────────────────── +function buildConstraints(){ + const names=constraintNames(); + // preserve existing statuses if rebuilding + const prev={}; pkgConstraints.forEach(c=>prev[c.name]=c); + pkgConstraints=names.map(n=> prev[n] || {name:n, status:'open', comment:''}); + const tb=document.getElementById('constraint-body'); tb.innerHTML=''; + pkgConstraints.forEach((c,i)=>{ const tr=document.createElement('tr'); + tr.innerHTML=`${esc(c.name)} + + + + + + `; + tb.appendChild(tr); }); +} +function setConstraint(i,val){ + const before=pkgConstraints[i].status; + pkgConstraints[i].status=val; buildConstraints(); updateReleaseBanner(); + // If a constraint is flagged Open after the package was released, require logging the issue + if(val==='open' && STATUS_ORDER.indexOf(getRadio('status'))>=ISSUED_IDX){ + prevStatus=getRadio('status'); holdContext={index:i, before}; + openHoldModal(pkgConstraints[i].name, true); + } +} +function readiness(){ const open=pkgConstraints.filter(c=>c.status==='open').length; return {open, total:pkgConstraints.length, cleared:pkgConstraints.filter(c=>c.status==='cleared').length, ready:open===0}; } +function updateReleaseBanner(){ + const b=document.getElementById('release-banner'); const r=readiness(); const st=getRadio('status'); + let cls, txt; + if(st==='Issue'){ cls='rb-hold'; txt=`⛔ On hold — ${r.open} constraint${r.open===1?'':'s'} reopened. Resolve to resume.`; } + else if(r.ready){ cls='rb-ready'; txt=`✓ Release-ready — all ${r.total} constraints cleared or N/A.`; } + else { cls='rb-notready'; txt=`⚠ Not release-ready — ${r.open} of ${r.total} constraint${r.open===1?'':'s'} still open.`; } + b.innerHTML=`
${txt}
`; +} +function onStatusChange(target){ + const idx=STATUS_ORDER.indexOf(target); + if(idx>=ISSUED_IDX && readiness().open>0){ + const open=pkgConstraints.filter(c=>c.status==='open').map(c=>c.name); + alert('Cannot move to "'+target+'" — these constraints are still open:\n\n• '+open.join('\n• ')+'\n\nClear or mark N/A first.'); + setRadio('status', prevStatus||'Scheduled'); updateReleaseBanner(); return; + } + if(target==='Issue'){ openHoldModal(); return; } // modal commits or reverts + prevStatus=target; updateReleaseBanner(); track('status_change',{status:target}); +} + +// ── HOLD LOG MODAL (comments 7) ────────────────────────────────────────────── +let holdPhotoData='', holdContext=null; +function openHoldModal(preselect, fromConstraint){ + holdPhotoData=''; if(!fromConstraint) holdContext=null; + const sel=document.getElementById('hold-constraint'); + sel.innerHTML = pkgConstraints.map(c=>``).join('') + ''; + const existing = fromConstraint ? (pkgConstraints.find(c=>c.name===preselect)||{}).comment||'' : ''; + document.getElementById('hold-details').value=existing; + document.getElementById('hold-doclink').value=''; + document.getElementById('hold-photo-preview').innerHTML=''; + document.getElementById('hold-modal').classList.add('open'); +} +function holdPhotoChange(ev){ + const f=ev.target.files&&ev.target.files[0]; if(!f) return; + const r=new FileReader(); r.onload=()=>{ holdPhotoData=r.result; document.getElementById('hold-photo-preview').innerHTML=`supporting photo`; }; r.readAsDataURL(f); +} +function submitHold(){ + const constraint=document.getElementById('hold-constraint').value; + const details=document.getElementById('hold-details').value.trim(); + const doclink=document.getElementById('hold-doclink').value.trim(); + if(!details){ alert('A comment defining the issue is required.'); return; } + pkgHolds.push({ ts:new Date().toISOString(), constraint, details, doc:doclink, photo:holdPhotoData||'' }); + const c=pkgConstraints.find(x=>x.name===constraint); if(c){ c.status='open'; c.comment=details; } + buildConstraints(); setRadio('status','Issue'); prevStatus='Issue'; holdContext=null; + document.getElementById('hold-modal').classList.remove('open'); + updateReleaseBanner(); toast('Hold logged'); track('hold_logged',{constraint}); +} +function cancelHold(){ + if(holdContext){ // came from flagging a constraint on a released package -> revert that constraint + pkgConstraints[holdContext.index].status=holdContext.before; holdContext=null; buildConstraints(); updateReleaseBanner(); + document.getElementById('hold-modal').classList.remove('open'); return; + } + closeHoldModal(false); +} +function closeHoldModal(committed){ + document.getElementById('hold-modal').classList.remove('open'); + if(!committed){ setRadio('status', prevStatus||'Scheduled'); updateReleaseBanner(); } +} + +// ── SIGN-OFFS ──────────────────────────────────────────────────────────────── +function sopNameForSignoff(role){ + if(!SOP) return ''; + const roles=SOP.roles||[]; const proj=SOP.project||{}; + const find=(re)=>{ const m=roles.find(r=>re.test(r.role||'')); return m&&m.name||''; }; + switch(role){ + case 'Planner': return find(/planner/i); + case 'Superintendent': return find(/superintendent/i) || proj.cm || ''; + case 'HSE Professional': return find(/safety|hse|ehs/i); + case 'Quality Representative': return find(/quality/i) || proj.qm || ''; + case 'Work Foreman': return find(/general foreman|foreman/i); + } + return ''; +} +function buildSignoffs(){ + const prev={}; pkgSignoffs.forEach(s=>prev[s.role]=s); + pkgSignoffs=SIGNOFF_ROLES.map(r=>{ + if(prev[r]) return prev[r]; + return {role:r, name:sopNameForSignoff(r), date:'', signed:false, fromSOP:!!sopNameForSignoff(r)}; + }); + const tb=document.getElementById('signoff-body'); tb.innerHTML=''; + pkgSignoffs.forEach((s,i)=>{ const tr=document.createElement('tr'); + tr.innerHTML=`${esc(s.role)} + ${s.fromSOP?'from SOP':''} + ${s.date?esc(s.date):'—'} ${s.dateReason?`overridden`:''} + `; + tb.appendChild(tr); }); +} +function todayStr(){ const d=new Date(); return d.getFullYear()+'-'+pad2(d.getMonth()+1)+'-'+pad2(d.getDate()); } +function onSignoffSigned(i,checked){ + pkgSignoffs[i].signed=checked; + if(checked && !pkgSignoffs[i].date){ pkgSignoffs[i].date=todayStr(); pkgSignoffs[i].dateReason=''; } + buildSignoffs(); track('signoff',{role:pkgSignoffs[i].role, signed:checked}); +} +function onSignoffDateOverride(i){ + const s=pkgSignoffs[i]; + const nd=prompt('Manual sign-off date for '+s.role+' (YYYY-MM-DD). The date is normally set automatically when Signed is checked.', s.date||todayStr()); + if(nd===null) return; + if(!/^\d{4}-\d{2}-\d{2}$/.test(nd.trim())){ alert('Enter the date as YYYY-MM-DD.'); return; } + const reason=prompt('Reason for manually overriding the '+s.role+' sign-off date:'); + if(reason===null || !reason.trim()){ alert('A reason is required for a manual date override.'); return; } + s.date=nd.trim(); s.dateReason=reason.trim(); buildSignoffs(); track('signoff_date_override',{role:s.role}); +} + +// ── SAVE / OUTPUT ──────────────────────────────────────────────────────────── +function collectPackage(){ + const steps=pkgWorkSteps.map(s=>s.trim()).filter(Boolean); + return { + id: editingId || ('wp_'+Date.now().toString(36)+Math.random().toString(36).slice(2,5)), + number:gv('wp_number'), status:getRadio('status')||'Draft', subject:gv('wp_subject'), + type:gv('wp_type'), discipline:derivedDiscipline(), system:gv('wp_system'), location:gv('wp_location'), + cost:gv('wp_cost'), wbs:gv('wp_wbs'), assignees:gv('wp_assignees'), distribution:gv('wp_distribution'), + due:gv('wp_due'), spec:gv('wp_spec'), desc:gv('wp_desc'), + work:steps.join('\n'), workSteps:steps, numberDims:{...numberDims}, + hours:gv('wp_hours'), seq:gv('wp_seq'), + materials:pkgMaterials.filter(m=>m.qty||m.desc), attachments:pkgAttach.filter(a=>a.doc), + kitStatus:gv('wp_kit_status'), kitOwner:gv('wp_kit_owner'), kitDate:gv('wp_kit_date'), + mimoTime:gv('wp_mimo_time'), mimoLoc:gv('wp_mimo_loc'), + constraints:pkgConstraints.map(c=>({name:c.name,status:c.status,comment:c.comment})), + qc:gv('wp_qc'), photo:gv('wp_photo'), hold:gv('wp_hold'), + qcFromSOP: !pkgOverrides['wp_qc'], photoFromSOP: !pkgOverrides['wp_photo'], holdFromSOP: !pkgOverrides['wp_hold'], overrides:{...pkgOverrides}, + signoffs:pkgSignoffs.map(s=>({role:s.role,name:s.name,date:s.date,signed:s.signed,dateReason:s.dateReason||''})), + holds:pkgHolds.map(h=>({...h})), + actualHrs:gv('wp_actual_hrs'), installedQty:gv('wp_installed_qty'), redlines:gv('wp_redlines'), lessons:gv('wp_lessons'), + project:(SOP&&SOP.project&&SOP.project.name)||'', track:(SOP&&SOP.field&&SOP.field.trackPlatform)||'', + updatedAt:new Date().toISOString() + }; +} +function savePackage(view){ + if(!gv('wp_subject') || !gv('wp_type')){ alert('Subject and WP Type are required.'); return; } + const pkg=collectPackage(); + const ix=savedPackages.findIndex(p=>p.id===pkg.id); + if(ix>=0) savedPackages[ix]=pkg; else savedPackages.push(pkg); + editingId=pkg.id; saveStore(); renderSavedList(); track('package_saved',{status:pkg.status}); + document.getElementById('loadingOverlay').classList.add('active'); + setTimeout(()=>{ document.getElementById('loadingOverlay').classList.remove('active'); if(view) renderPackage(pkg); }, 400); +} +function renderPackage(pkg){ + const r = pkg.constraints ? {open:pkg.constraints.filter(c=>c.status==='open').length,total:pkg.constraints.length} : {open:0,total:0}; + const readyTxt = pkg.status==='Issue' ? 'ON HOLD' : (r.open===0?'RELEASE-READY':(r.open+' OPEN CONSTRAINTS')); + let h=`

${esc(pkg.number||'(no number)')} — Work Package

+
${esc(pkg.project)} · TYPE: ${esc(pkg.type).toUpperCase()} · STATUS: ${esc(pkg.status).toUpperCase()} · ${readyTxt}
`; + const costDesc = (COST_CODES.find(c=>c.split('|')[0]===pkg.cost)||'').split('|')[1]; + h+=`

1.0 General Information

+ + + + + + + + + + + + +
WP Number${cell(pkg.number)}
Subject${cell(pkg.subject)}
Type / Discipline${cell(pkg.type)}${pkg.discipline?' · '+esc(pkg.discipline):''}
System / Facility Code / UPN${cell(pkg.system)}
Location${cell(pkg.location)}
Cost Code${pkg.cost?esc(pkg.cost)+(costDesc?' — '+esc(costDesc):''):ns()}
Acumatica Task${cell(pkg.wbs)}
Assignees${cell(pkg.assignees)}
Distribution${cell(pkg.distribution)}
Due Date${cell(pkg.due)}
Specification Section${cell(pkg.spec)}
Description${cell(pkg.desc)}
`; + const stepsArr = pkg.workSteps && pkg.workSteps.length ? pkg.workSteps : (pkg.work?String(pkg.work).split('\n').filter(Boolean):[]); + const stepsHtml = stepsArr.length ? '
    '+stepsArr.map(s=>`
  1. ${esc(s)}
  2. `).join('')+'
' : ns(); + h+=`

2.0 Scope & Work

+ + + +
Description of Work${stepsHtml}
Labor Est. Hrs.${cell(pkg.hours)}
Package Predecessor${pkg.seq?('After: '+esc(pkg.seq)):'None (no predecessor)'}
`; + if(pkg.materials&&pkg.materials.length){ h+=`

3.0 Material List

`; + pkg.materials.forEach(m=>h+=``); h+=`
QtyUnitDescription
${cell(m.qty)}${cell(m.unit)}${cell(m.desc)}
`; } + if(pkg.attachments&&pkg.attachments.length){ h+=`

4.0 Drawings & Attachments

`; + pkg.attachments.forEach(a=>h+=``); h+=`
DocumentRevLink / Note
${cell(a.doc)}${cell(a.rev)}${a.link?linkify(a.link):ns()}
`; } + h+=`

5.0 Kitting & MIMO

+ + + + +
Kitting Status${cell(pkg.kitStatus)}
Warehouse Owner${cell(pkg.kitOwner)}
Kitting Need Date${cell(pkg.kitDate)}
MIMO Sch. Time / Location${cell(pkg.mimoTime)} ${pkg.mimoLoc?'· '+esc(pkg.mimoLoc):''}
`; + h+=`

6.0 Constraints — Release Readiness

`; + (pkg.constraints||[]).forEach(c=>{ const lbl=c.status==='cleared'?'Cleared':c.status==='na'?'N/A':'Open'; const col=c.status==='cleared'?'var(--accent-green)':c.status==='na'?'var(--text-dim)':'var(--red)'; + h+=``; }); + h+=`
ConstraintStatusComment
${esc(c.name)}${lbl}${cell(c.comment)}
`; + h+=`

7.0 Quality & Hold Points

+ + + +
QC${cell(pkg.qc)}${pkg.overrides&&pkg.overrides.wp_qc?` (overridden: ${esc(pkg.overrides.wp_qc)})`:(pkg.qcFromSOP?' [from SOP]':'')}
Photo Documentation${cell(pkg.photo)}${pkg.overrides&&pkg.overrides.wp_photo?` (overridden: ${esc(pkg.overrides.wp_photo)})`:(pkg.photoFromSOP?' [from SOP]':'')}
Witness / Hold Points${cell(pkg.hold)}
`; + if(pkg.holds&&pkg.holds.length){ + h+=`

7.5 Hold Log

`; + pkg.holds.forEach(hd=>{ const when=hd.ts?new Date(hd.ts).toLocaleString():''; const sup=[hd.doc?linkify(hd.doc):'', hd.photo?'photo attached':''].filter(Boolean).join('
')||ns(); + h+=``; }); + h+=`
LoggedConstraintDetailsSupport
${esc(when)}${cell(hd.constraint)}${cell(hd.details)}${sup}
`; + } + h+=`

8.0 Approvals & Sign-offs

`; + (pkg.signoffs||[]).forEach(s=>h+=``); + h+=`
RoleNameDateSigned
${esc(s.role)}${cell(s.name)}${cell(s.date)}${s.signed?'✓':'—'}
`; + if(pkg.actualHrs||pkg.installedQty||pkg.redlines||pkg.lessons){ h+=`

9.0 Closeout

+ + + + +
Actual Hrs.${cell(pkg.actualHrs)}
Installed Quantity${cell(pkg.installedQty)}
Redlines / As-Built${cell(pkg.redlines)}
Lessons Learned${cell(pkg.lessons)}
`; } + h+=`

Pushed to: ${cell(pkg.track)}

`; + document.getElementById('pkg-doc').innerHTML=h; showOutput(); +} +function printPackage(){ + const c=document.getElementById('pkg-doc').innerHTML; const w=window.open('','_blank'); + w.document.write(`Work Package${c}`); + w.document.close(); w.print(); +} + +// ── VIEWS ──────────────────────────────────────────────────────────────────── +function showOutput(){ document.querySelectorAll('.main > .card, .main > .nav-row').forEach(e=>e.style.display='none'); document.getElementById('pkg-output').style.display=''; renderSavedList(); currentView='Package View'; window.scrollTo({top:0,behavior:'smooth'}); } +function showForm(){ document.querySelectorAll('.main > .card').forEach(e=>e.style.display=''); document.querySelector('.main > .nav-row').style.display='flex'; document.getElementById('pkg-output').style.display='none'; document.getElementById('saved-card').style.display = savedPackages.length?'':'none'; currentView='Work Package Form'; window.scrollTo({top:0,behavior:'smooth'}); } + +// ── SAVED PACKAGES ─────────────────────────────────────────────────────────── +const STORE_KEY='wp_iwp_v1'; +function saveStore(){ try{ localStorage.setItem(STORE_KEY, JSON.stringify(savedPackages)); }catch(e){} } +function loadStore(){ try{ const d=JSON.parse(localStorage.getItem(STORE_KEY)); if(Array.isArray(d)) savedPackages=d; }catch(e){} } +function renderSavedList(){ + const card=document.getElementById('saved-card'), body=document.getElementById('saved-body'); + 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=''; + body.innerHTML=savedPackages.map((p,i)=>{ const open=(p.constraints||[]).filter(c=>c.status==='open').length; + const ready = p.status==='Issue' ? 'On Hold' : (open===0?'Ready':`${open} open`); + return `${esc(p.number||'—')}${esc(p.type||'')}${esc(p.subject||'')} + ${esc(p.status||'')}${ready} + `; + }).join(''); +} +function viewPackage(i){ if(savedPackages[i]) renderPackage(savedPackages[i]); } +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; savedPackages.splice(i,1); saveStore(); renderSavedList(); track('package_deleted'); } +function clearSaved(){ if(!savedPackages.length) return; if(!confirm('Delete all '+savedPackages.length+' saved packages on this device?')) return; savedPackages=[]; saveStore(); renderSavedList(); } +function editPackage(i){ const p=savedPackages[i]; if(!p) return; editingId=p.id; loadPackageIntoForm(p); } +function loadExample(){ loadPackageIntoForm(JSON.parse(JSON.stringify(EXAMPLE_PKG))); editingId=null; toast('Example work package loaded'); track('example_loaded'); } +function loadPackageIntoForm(p){ + const set=(id,v)=>{const el=document.getElementById(id); if(el) el.value=v||'';}; + set('wp_subject',p.subject); set('wp_system',p.system); set('wp_location',p.location); + set('wp_assignees',p.assignees); set('wp_distribution',p.distribution); + set('wp_due',p.due); set('wp_spec',p.spec); set('wp_desc',p.desc); set('wp_hours',p.hours); + set('wp_kit_owner',p.kitOwner); set('wp_kit_date',p.kitDate); set('wp_mimo_time',p.mimoTime); set('wp_mimo_loc',p.mimoLoc); + set('wp_actual_hrs',p.actualHrs); set('wp_installed_qty',p.installedQty); set('wp_redlines',p.redlines); set('wp_lessons',p.lessons); + buildTypePicker(); document.getElementById('wp_type').value=p.type||''; + buildCostCodes(); document.getElementById('wp_cost').value=p.cost||''; + set('wp_wbs',p.wbs); + document.getElementById('wp_kit_status').value=p.kitStatus||''; + buildSequencePicker(); document.getElementById('wp_seq').value=p.seq||''; + setRadio('status',p.status||'Draft'); + // number dimensions + numberDims = p.numberDims ? {...p.numberDims} : {}; buildNumberDims(); + // derived discipline + const dd=document.getElementById('wp_discipline_derived'); if(dd) dd.textContent=derivedDiscipline()||'—'; + // overrides + locked quality/hold + pkgOverrides=p.overrides?{...p.overrides}:{}; + set('wp_qc', p.qc!=null?p.qc:sopValueFor('wp_qc')); + set('wp_photo', p.photo!=null?p.photo:sopValueFor('wp_photo')); + set('wp_hold', (p.hold&&p.hold.trim())?p.hold:sopValueFor('wp_hold')); + lockQuality('wp_qc'); lockQuality('wp_photo'); lockQuality('wp_hold'); + // collections + pkgMaterials=(p.materials&&p.materials.length)?p.materials.map(m=>({...m,unit:(m.unit||'').toUpperCase()})):[{qty:'',unit:'',desc:''}]; buildMaterials(); + pkgAttach=(p.attachments&&p.attachments.length)?p.attachments.map(a=>({...a})):[{doc:'',rev:'',link:''}]; buildAttach(); + pkgWorkSteps=(p.workSteps&&p.workSteps.length)?p.workSteps.slice():(p.work?String(p.work).split('\n').filter(Boolean):['']); if(!pkgWorkSteps.length)pkgWorkSteps=['']; buildWorkSteps(); + pkgConstraints=(p.constraints||[]).map(c=>({...c})); if(!pkgConstraints.length) buildConstraints(); else renderConstraintRows(); + pkgSignoffs=(p.signoffs||[]).map(s=>({...s, fromSOP:!!s.name})); if(!pkgSignoffs.length) buildSignoffs(); else renderSignoffRows(); + pkgHolds=(p.holds||[]).map(h=>({...h})); + updateNumber(); updateReleaseBanner(); prevStatus=p.status||'Draft'; showForm(); +} +function renderConstraintRows(){ const tmp=pkgConstraints; pkgConstraints=[]; buildConstraints(); + tmp.forEach(s=>{ const c=pkgConstraints.find(x=>x.name===s.name); if(c){ c.status=s.status; c.comment=s.comment; }}); + buildConstraints(); } +function renderSignoffRows(){ const tmp=pkgSignoffs; pkgSignoffs=[]; buildSignoffs(); tmp.forEach(s=>{ const c=pkgSignoffs.find(x=>x.role===s.role); if(c){ c.name=s.name; c.date=s.date; c.signed=s.signed; c.dateReason=s.dateReason||''; }}); buildSignoffs(); } + +function newPackage(){ + editingId=null; + ['wp_subject','wp_system','wp_location','wp_wbs','wp_assignees','wp_distribution','wp_due','wp_spec','wp_desc','wp_hours','wp_kit_owner','wp_kit_date','wp_mimo_time','wp_mimo_loc','wp_actual_hrs','wp_installed_qty','wp_redlines','wp_lessons'].forEach(id=>{const el=document.getElementById(id); if(el) el.value='';}); + document.getElementById('wp_type').value=''; document.getElementById('wp_kit_status').value=''; document.getElementById('wp_cost').value=''; + const dd=document.getElementById('wp_discipline_derived'); if(dd) dd.textContent='—'; + setRadio('status','Draft'); + numberDims={}; buildNumberDims(); + pkgMaterials=[{qty:'',unit:'',desc:''}]; buildMaterials(); + pkgAttach=[{doc:'',rev:'',link:''}]; buildAttach(); + pkgWorkSteps=['']; buildWorkSteps(); + pkgConstraints=[]; buildConstraints(); pkgSignoffs=[]; buildSignoffs(); + pkgHolds=[]; pkgOverrides={}; + set_quality_from_sop(); lockQuality('wp_qc'); lockQuality('wp_photo'); lockQuality('wp_hold'); + prevStatus='Draft'; + updateNumber(); updateReleaseBanner(); showForm(); 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(){ + if(!savedPackages.length){ alert('No packages saved yet.'); return; } + const payload={tool:'Work Package (IWP)', project:(SOP&&SOP.project&&SOP.project.name)||'', exportedAt:new Date().toISOString(), packages:savedPackages}; + const blob=new Blob([JSON.stringify(payload,null,2)],{type:'application/json'}); const a=document.createElement('a'); + a.href=URL.createObjectURL(blob); a.download='work-packages-'+new Date().toISOString().slice(0,10)+'.json'; + document.body.appendChild(a); a.click(); a.remove(); setTimeout(()=>URL.revokeObjectURL(a.href),1000); track('packages_exported',{count:savedPackages.length}); +} + +// ── VIEW SOP REFERENCE (comment 2) ─────────────────────────────────────────── +function openSopModal(){ + if(!SOP){ alert('No SOP loaded.'); return; } + const p=SOP.project||{}, g=SOP.governance||{}, q=SOP.quality||{}; + const row=(k,v)=>`${esc(k)}${v||'—'}`; + let h=``; + h+=row('Project', esc(p.name)); h+=row('Number', esc(p.number)); h+=row('Client', esc(p.client)); + h+=row('Division', esc(p.division)); h+=row('PM / CM / QM', [p.pm,p.cm,p.qm].filter(Boolean).map(esc).join(' / ')); + h+=row('WP number format', `${esc(g.woFormat||'—')}`); h+=row('Issuance', esc((g.issuance||[]).join(', '))); + h+=row('Target WP size', esc(g.woSize)); + h+=row('WP types', enabledTypes().map(t=>esc(t.name)).join(', ')); + h+=row('QC', esc([q.qcReq,q.qcScope].filter(Boolean).join(' — '))); h+=row('Photo', esc(q.photo)); + h+=row('Hold points', esc(q.holdPoints)); h+=row('Tracking tool', esc((SOP.field&&SOP.field.trackPlatform))); + h+=row('Sequence', ((SOP.sequence||[]).map(s=>esc(s.label)+(s.kind==='gate'?' (gate)':'')).join(' → '))); + h+=row('Roles', (SOP.roles||[]).map(r=>esc(r.role)+': '+esc(r.name)).join('; ')); + h+=row('Sources', (SOP.sources||[]).map(s=>esc(s.label)+' ('+esc(s.system)+')').join('; ')); + h+=`
`; + document.getElementById('sop-modal-body').innerHTML=h; + document.getElementById('sop-modal').classList.add('open'); track('view_sop'); +} +function closeSopModal(){ document.getElementById('sop-modal').classList.remove('open'); } +const ANALYTICS_KEY='wp_iwp_analytics_v1'; const _session='s_'+Date.now().toString(36)+Math.random().toString(36).slice(2,6); +function analyticsLoad(){ try{ return JSON.parse(localStorage.getItem(ANALYTICS_KEY))||{events:[]}; }catch(e){ return {events:[]}; } } +function analyticsSave(d){ try{ localStorage.setItem(ANALYTICS_KEY, JSON.stringify(d)); }catch(e){} } +function track(event,detail){ if(devMode) return; try{ const d=analyticsLoad(); d.events.push({ts:new Date().toISOString(),session:_session,event,detail:detail||null}); if(d.events.length>5000)d.events=d.events.slice(-5000); analyticsSave(d); }catch(e){} } +function showAnalytics(){ const d=analyticsLoad(); const by={}; const ses=new Set(); d.events.forEach(e=>{by[e.event]=(by[e.event]||0)+1;ses.add(e.session);}); + let t=`USAGE ANALYTICS\n\nSessions: ${ses.size} Events: ${d.events.length}\n\nActions:\n`; Object.keys(by).forEach(k=>t+=` ${k}: ${by[k]}\n`); t+=`\nSaved packages (this device): ${savedPackages.length}\n\nDownload full log as JSON?`; + if(confirm(t)) downloadAnalytics(); } +function downloadAnalytics(){ const d=analyticsLoad(); const blob=new Blob([JSON.stringify(d,null,2)],{type:'application/json'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='wp-iwp-usage-'+new Date().toISOString().slice(0,10)+'.json'; document.body.appendChild(a); a.click(); a.remove(); setTimeout(()=>URL.revokeObjectURL(a.href),1000); track('analytics_exported'); } + +// ── COMMENTS ───────────────────────────────────────────────────────────────── +const COMMENTS_KEY='wp_iwp_comments_v1'; +function cmtLoad(){ let d; try{ d=JSON.parse(localStorage.getItem(COMMENTS_KEY)); }catch(e){} if(!d||typeof d!=='object')d={}; if(!d.clientId)d.clientId='c_'+Date.now().toString(36)+Math.random().toString(36).slice(2,6); if(typeof d.author!=='string')d.author=''; if(!Array.isArray(d.comments))d.comments=[]; return d; } +function cmtSave(d){ try{ localStorage.setItem(COMMENTS_KEY, JSON.stringify(d)); }catch(e){} } +function cmtSaveAuthor(v){ const d=cmtLoad(); d.author=v; cmtSave(d); } +function toggleComments(){ const dr=document.getElementById('cmt-drawer'),ov=document.getElementById('cmt-overlay'); const open=!dr.classList.contains('open'); dr.classList.toggle('open',open); ov.classList.toggle('open',open); dr.setAttribute('aria-hidden',open?'false':'true'); if(open){ cmtUpdateCurStep(); renderComments(); const a=document.getElementById('cmt-author'); if(a&&!a.value)a.focus(); else document.getElementById('cmt-input')?.focus(); } } +function cmtUpdateCurStep(){ const el=document.getElementById('cmt-cur-step'); if(el) el.textContent=currentView; } +function addComment(){ const d=cmtLoad(); const author=(document.getElementById('cmt-author').value||'').trim(); const text=(document.getElementById('cmt-input').value||'').trim(); if(!author){ alert('Please add your name first.'); document.getElementById('cmt-author').focus(); return; } if(!text){ document.getElementById('cmt-input').focus(); return; } d.author=author; d.comments.push({id:'m_'+Date.now().toString(36)+Math.random().toString(36).slice(2,5), view:currentView, author, clientId:d.clientId, text, ts:new Date().toISOString()}); cmtSave(d); document.getElementById('cmt-input').value=''; renderComments(); refreshCommentBadges(); track('comment_added'); } +function deleteComment(id){ const d=cmtLoad(); d.comments=d.comments.filter(c=>c.id!==id); cmtSave(d); renderComments(); refreshCommentBadges(); } +function renderComments(){ const d=cmtLoad(); const list=document.getElementById('cmt-list'); if(!list) return; if(!d.comments.length){ list.innerHTML='
No comments yet.
'; return; } const sorted=[...d.comments].sort((a,b)=>(b.ts||'').localeCompare(a.ts||'')); list.innerHTML=sorted.map(c=>{ const mine=c.clientId===d.clientId; const when=c.ts?new Date(c.ts).toLocaleString():''; return `
${esc(c.author||'Anonymous')}${esc(c.view||'')}${esc(when)}
${esc(c.text)}
${mine?`
`:''}
`; }).join(''); } +function refreshCommentBadges(){ const d=cmtLoad(); const t=document.getElementById('cbadge-total'); if(t){ if(d.comments.length){ t.style.display=''; t.textContent=d.comments.length; } else t.style.display='none'; } } +function exportComments(){ const d=cmtLoad(); const payload={tool:'Work Package (IWP)', exportedBy:d.author||'', exportedAt:new Date().toISOString(), clientId:d.clientId, comments:d.comments}; const blob=new Blob([JSON.stringify(payload,null,2)],{type:'application/json'}); const safe=(d.author||'review').replace(/[^a-z0-9]+/gi,'-').toLowerCase(); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download=`wp-iwp-comments-${safe}-${new Date().toISOString().slice(0,10)}.json`; document.body.appendChild(a); a.click(); a.remove(); setTimeout(()=>URL.revokeObjectURL(a.href),1000); track('comments_exported'); } +function importComments(ev){ const f=ev.target.files&&ev.target.files[0]; if(!f) return; const r=new FileReader(); r.onload=()=>{ try{ const inc=JSON.parse(r.result); const incoming=Array.isArray(inc)?inc:(inc.comments||[]); if(!incoming.length){ alert('No comments found.'); return; } const d=cmtLoad(); const seen=new Set(d.comments.map(c=>c.id)); let added=0; incoming.forEach(c=>{ if(c&&c.id&&!seen.has(c.id)){ d.comments.push(c); seen.add(c.id); added++; }}); cmtSave(d); renderComments(); refreshCommentBadges(); alert(`Imported ${added} comment${added===1?'':'s'}.`); }catch(e){ alert('Could not read that file.'); } ev.target.value=''; }; r.readAsText(f); } +function clearMyComments(){ const d=cmtLoad(); const mine=d.comments.filter(c=>c.clientId===d.clientId).length; if(!mine){ alert('No comments to clear.'); return; } if(!confirm(`Delete your ${mine} comment(s)?`)) return; d.comments=d.comments.filter(c=>c.clientId!==d.clientId); cmtSave(d); renderComments(); refreshCommentBadges(); } +function cmtInit(){ const d=cmtLoad(); cmtSave(d); const a=document.getElementById('cmt-author'); if(a)a.value=d.author||''; cmtUpdateCurStep(); renderComments(); refreshCommentBadges(); } + +// ── STATUS PILLS ───────────────────────────────────────────────────────────── +document.querySelectorAll('#status-group .radio-pill').forEach(p=>p.addEventListener('click',e=>{ + if(e.target.tagName==='INPUT') return; + const v=p.dataset.val; + prevStatus = getRadio('status') || 'Draft'; // capture before switching (for revert) + document.querySelectorAll('#status-group .radio-pill').forEach(x=>x.classList.remove('selected')); + p.classList.add('selected'); p.querySelector('input').checked=true; + onStatusChange(v); +})); + +// ── BOOT ───────────────────────────────────────────────────────────────────── +loadStore(); +loadSampleSOP(); +setRadio('status','Draft'); +renderSavedList(); +cmtInit(); +track('app_open'); diff --git a/wp-creation-index.html b/wp-creation-index.html new file mode 100644 index 0000000..02b64ba --- /dev/null +++ b/wp-creation-index.html @@ -0,0 +1,235 @@ + + + + + +Work Package (IWP) — Prime Controls + + + + + +
Saving work package…
+ +
+
+ + +
+
|
+
Work Package (IWP)
+ + + + + + + + +
+ + + +
+ + +
+ +
+ + +
+
General Information
+
Parameters in blue are inherited from the project SOP. Fill the rest for this package.
+
+
+
+
+ + + + + + + +
+
Cannot move to Issued or beyond until all constraints are cleared.
+
+
+
WP number builds automatically from these scope fields + the WP type (per the SOP naming format):
+
+
+
+
from SOP types
+
derived from WP type · drives the readiness checklist
+
+
+
Acumatica cost codes
+
+
+
+
+
+
+
+
+
+
+ + +
+
Scope & Work
+
+
Enter the work as ordered steps — added in sequence, the way the crew performs them.
+
+ +
+
+
+
The package/step (from the SOP sequence) that must finish before this work can start. Choose "None" if it has no predecessor.
+
+
+ + +
+
Material List
+
Structured bill of materials. Feeds kitting and the delivery forecast. Unit is from the Acumatica unit list.
+
QtyUnitDescription
+
+ + + + +
+
+ + +
+
Drawings & Attachments
+ +
Document / DrawingRevLink / Note
+ +
+ + +
+
Kitting & Material Movement (MIMO)
+
+
+
+
+
+
scheduled material-move date & time
+
+
+
+ + +
+
Constraints — Release Readiness
+
Per AWP, a package is not released to the field until every constraint is Cleared or N/A. If a constraint reopens after release, status drops to Issue (Hold).
+
ConstraintStatusComment
+
+ + +
+
Quality, Inspection & Hold Points
+
+
+
+
+
+
+
+
+
Inherited from the SOP. A Hold Point stops work until inspection sign-off; a Witness Point is offered for inspection but work may proceed if declined.
+
+ + +
+
Approvals & Sign-offs
+
Per the AWP IWP checklist. A package should be signed by these roles before release.
+
RoleNameDateSigned
+
+ + +
+
Closeout
+
Completed at QC / Closed — captures as-built reality and lessons learned.
+
+
+
+
+
+
+
+ + + + + + + + + +
+ + + + + + + + +
+ + + + + diff --git a/wp-creation-styles.css b/wp-creation-styles.css new file mode 100644 index 0000000..1771fe3 --- /dev/null +++ b/wp-creation-styles.css @@ -0,0 +1,559 @@ +@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300&display=swap'); + + :root { + --bg: #f4f5f7; + --surface: #ffffff; + --surface2: #f7f8fa; + --border: #e3e6ec; + --border-strong: #d0d5de; + --text: #1a2230; + --text-muted: #5a6675; + --text-dim: #9aa3b2; + --accent: #2563d6; + --accent-dim: #e8f0fe; + --accent-green: #15924f; + --accent-green-dim: #e4f6ec; + --accent-amber: #b87100; + --accent-amber-dim: #fdf2e0; + --red: #cf3b3b; + --red-dim: #fbeaea; + --radius: 5px; + --shadow: 0 1px 2px rgba(20,30,50,.04), 0 1px 3px rgba(20,30,50,.06); + --shadow-lg: 0 4px 16px rgba(20,30,50,.08); + --mono: 'IBM Plex Mono', monospace; + --sans: 'IBM Plex Sans', sans-serif; + } + + * { box-sizing: border-box; margin: 0; padding: 0; } + + body { + background: var(--bg); + color: var(--text); + font-family: var(--sans); + font-size: 14px; + line-height: 1.6; + min-height: 100vh; + } + + /* ── HEADER ── */ + .header { + background: var(--surface); + border-bottom: 1px solid var(--border); + padding: 14px 32px; + display: flex; + align-items: center; + gap: 14px; + position: sticky; + top: 0; + z-index: 100; + box-shadow: var(--shadow); + } + .header-logo { + font-family: var(--mono); + font-size: 11px; + font-weight: 600; + letter-spacing: .15em; + color: var(--accent); + text-transform: uppercase; + } + .header-sep { color: var(--border-strong); } + .header-title { font-size: 13px; font-weight: 500; color: var(--text-muted); } + + /* ── STEPPER ── */ + .stepper-wrap { padding: 22px 32px 0; max-width: 1000px; margin: 0 auto; } + .stepper { + display: flex; + flex-wrap: wrap; + gap: 0; + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; + background: var(--surface); + box-shadow: var(--shadow); + } + .step-tab { + flex: 1 0 10%; + padding: 9px 6px; + text-align: center; + font-family: var(--mono); + font-size: 9.5px; + font-weight: 500; + letter-spacing: .04em; + color: var(--text-dim); + background: var(--surface); + border-right: 1px solid var(--border); + border-bottom: 2px solid transparent; + cursor: pointer; + transition: all .15s; + line-height: 1.35; + text-transform: uppercase; + } + .step-tab:hover { background: var(--surface2); color: var(--text-muted); } + .step-tab.active { background: var(--accent-dim); color: var(--accent); border-bottom-color: var(--accent); } + .step-tab.done { color: var(--accent-green); background: var(--accent-green-dim); } + .step-num { display: block; font-size: 9px; opacity: .65; margin-bottom: 2px; } + + /* ── MAIN ── */ + .main { max-width: 1000px; margin: 0 auto; padding: 28px 32px 64px; } + + .section { display: none; } + .section.active { display: block; animation: fade .25s ease; } + @keyframes fade { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } } + + .card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: var(--shadow); + padding: 28px 32px; + } + + .section-header { margin-bottom: 24px; padding-bottom: 14px; border-bottom: 1px solid var(--border); } + .section-label { + font-family: var(--mono); font-size: 10px; font-weight: 600; + letter-spacing: .2em; color: var(--accent); text-transform: uppercase; margin-bottom: 5px; + } + .section-title { font-size: 22px; font-weight: 300; color: var(--text); letter-spacing: -.02em; } + .section-desc { margin-top: 5px; font-size: 13px; color: var(--text-muted); font-weight: 400; } + + /* ── FIELDS ── */ + .field-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin-bottom: 18px; } + .field-grid.col3 { grid-template-columns: 1fr 1fr 1fr; } + .field-grid.col1 { grid-template-columns: 1fr; } + .field { display: flex; flex-direction: column; gap: 6px; } + .field.span2 { grid-column: span 2; } + + label { + font-family: var(--mono); font-size: 10px; font-weight: 500; + letter-spacing: .1em; color: var(--text-muted); text-transform: uppercase; + } + label .req { color: var(--accent); margin-left: 3px; } + + input[type=text], input[type=date], input[type=number], textarea, select { + background: var(--surface); + border: 1px solid var(--border-strong); + border-radius: var(--radius); + color: var(--text); + font-family: var(--sans); + font-size: 14px; + padding: 9px 12px; + outline: none; + transition: border-color .15s, box-shadow .15s; + width: 100%; + } + input:focus, textarea:focus, select:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-dim); + } + textarea { resize: vertical; min-height: 70px; line-height: 1.5; } + ::placeholder { color: var(--text-dim); } + + /* ── PILLS ── */ + .check-group, .radio-group { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; } + .check-pill, .radio-pill { + display: flex; align-items: center; gap: 7px; + padding: 7px 13px; border: 1px solid var(--border-strong); border-radius: 20px; + cursor: pointer; transition: all .12s; user-select: none; font-size: 13px; color: var(--text-muted); + background: var(--surface); + } + .check-pill:hover, .radio-pill:hover { border-color: var(--accent); color: var(--text); } + .check-pill input, .radio-pill input { display: none; } + .check-pill .dot, .radio-pill .dot { + width: 7px; height: 7px; border-radius: 50%; background: var(--border-strong); + transition: background .12s; flex-shrink: 0; + } + .check-pill.checked { border-color: var(--accent); background: var(--accent-dim); color: var(--accent); } + .check-pill.checked .dot { background: var(--accent); } + .radio-pill.selected { border-color: var(--accent-amber); background: var(--accent-amber-dim); color: var(--accent-amber); } + .radio-pill.selected .dot { background: var(--accent-amber); } + + /* ── TABLES ── */ + .table-wrap { overflow-x: auto; margin-bottom: 22px; border: 1px solid var(--border); border-radius: var(--radius); } + table { width: 100%; border-collapse: collapse; } + th { + background: var(--surface2); border-bottom: 1px solid var(--border); border-right: 1px solid var(--border); + padding: 8px 10px; font-family: var(--mono); font-size: 9px; font-weight: 600; + letter-spacing: .1em; text-transform: uppercase; color: var(--text-muted); text-align: left; white-space: nowrap; + } + td { border-bottom: 1px solid var(--border); border-right: 1px solid var(--border); padding: 4px; vertical-align: middle; } + th:last-child, td:last-child { border-right: none; } + tr:last-child td { border-bottom: none; } + td.row-label { + padding: 8px 10px; font-family: var(--mono); font-size: 11px; color: var(--text-muted); + background: var(--surface2); white-space: nowrap; min-width: 150px; + } + td input[type=text], td select { border: 1px solid transparent; border-radius: 3px; background: transparent; font-size: 12px; padding: 6px 8px; } + td input[type=text]:focus, td select:focus { background: var(--surface); border-color: var(--accent); box-shadow: none; } + td.center { text-align: center; } + + .toggle-cell { display: flex; justify-content: center; align-items: center; padding: 6px; } + .toggle-btn { + width: 28px; height: 28px; border-radius: 4px; border: 1px solid var(--border-strong); + background: var(--surface); color: var(--text-dim); font-size: 15px; cursor: pointer; + display: flex; align-items: center; justify-content: center; transition: all .12s; font-family: var(--mono); line-height: 1; + } + .toggle-btn.enabled { border-color: var(--accent-green); background: var(--accent-green-dim); color: var(--accent-green); } + .toggle-btn.disabled { border-color: var(--red); background: var(--red-dim); color: var(--red); text-decoration: line-through; font-size: 11px; } + + .ov-select { width: 58px; font-size: 10px !important; padding: 5px 2px !important; text-align: center; font-family: var(--mono) !important; font-weight: 600; } + + /* ── MISC ── */ + .divider { border: none; border-top: 1px solid var(--border); margin: 24px 0; } + .sub-heading { + font-family: var(--mono); font-size: 10px; font-weight: 600; letter-spacing: .15em; + text-transform: uppercase; color: var(--text-muted); margin-bottom: 12px; display: flex; align-items: center; gap: 10px; + } + .sub-heading::after { content: ''; flex: 1; height: 1px; background: var(--border); } + + .notice { + background: var(--accent-dim); border: 1px solid #b9d2fb; border-radius: var(--radius); + padding: 10px 14px; font-size: 12px; color: #1a4fad; margin-bottom: 18px; font-family: var(--mono); + } + + /* ── DELIVERABLES ── */ + .deliv-group { margin-bottom: 20px; } + .deliv-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } + .deliv-item { + display: flex; align-items: flex-start; gap: 9px; padding: 9px 12px; + border: 1px solid var(--border-strong); border-radius: var(--radius); cursor: pointer; + transition: all .12s; user-select: none; background: var(--surface); + } + .deliv-item:hover { border-color: var(--accent); } + .deliv-item.checked { border-color: var(--accent); background: var(--accent-dim); } + .deliv-item input { display: none; } + .deliv-box { + width: 16px; height: 16px; border: 1.5px solid var(--border-strong); border-radius: 3px; + flex-shrink: 0; margin-top: 2px; display: flex; align-items: center; justify-content: center; + font-size: 11px; color: #fff; transition: all .12s; + } + .deliv-item.checked .deliv-box { background: var(--accent); border-color: var(--accent); } + .deliv-text { font-size: 12.5px; line-height: 1.35; color: var(--text); } + .deliv-text .dt-sub { display: block; font-size: 11px; color: var(--text-muted); margin-top: 1px; } + + /* ── NAV ── */ + .nav-row { display: flex; justify-content: space-between; align-items: center; padding-top: 24px; margin-top: 24px; border-top: 1px solid var(--border); } + .btn { + padding: 10px 22px; border-radius: var(--radius); font-family: var(--mono); font-size: 11px; font-weight: 600; + letter-spacing: .08em; text-transform: uppercase; cursor: pointer; border: 1px solid; transition: all .15s; + } + .btn-ghost { background: var(--surface); border-color: var(--border-strong); color: var(--text-muted); } + .btn-ghost:hover { border-color: var(--accent); color: var(--accent); } + .btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; box-shadow: var(--shadow); } + .btn-primary:hover { background: #1d52b8; } + .btn-generate { background: var(--accent-green); border-color: var(--accent-green); color: #fff; font-weight: 700; box-shadow: var(--shadow); } + .btn-generate:hover { background: #117a42; } + + /* ── OUTPUT ── */ + #output-section { display: none; } + #output-section.active { display: block; } + .output-toolbar { display: flex; gap: 10px; margin-bottom: 18px; align-items: center; flex-wrap: wrap; } + .output-doc { + background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); + box-shadow: var(--shadow-lg); padding: 52px 56px; font-family: var(--sans); line-height: 1.7; + color: var(--text); font-size: 13px; max-height: 74vh; overflow-y: auto; + } + .output-doc h1 { font-size: 22px; font-weight: 600; margin-bottom: 4px; letter-spacing: -.02em; } + .output-doc .doc-subtitle { font-size: 11px; color: var(--text-muted); margin-bottom: 28px; font-family: var(--mono); letter-spacing: .08em; } + .output-doc h2 { + font-size: 13px; font-weight: 600; font-family: var(--mono); letter-spacing: .08em; text-transform: uppercase; + color: var(--accent); margin-top: 30px; margin-bottom: 12px; padding-bottom: 6px; border-bottom: 2px solid var(--accent-dim); + } + .output-doc h3 { font-size: 11px; font-weight: 600; color: var(--text-muted); margin-top: 16px; margin-bottom: 6px; font-family: var(--mono); text-transform: uppercase; letter-spacing: .08em; } + .output-doc p { margin-bottom: 10px; } + .output-doc ul { padding-left: 20px; margin-bottom: 10px; } + .output-doc li { margin-bottom: 3px; } + .output-doc table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 12px; border: 1px solid var(--border); } + .output-doc th { background: var(--surface2); border: 1px solid var(--border); padding: 6px 10px; font-family: var(--mono); font-size: 9px; letter-spacing: .08em; text-transform: uppercase; text-align: left; color: var(--text-muted); } + .output-doc td { border: 1px solid var(--border); padding: 7px 10px; vertical-align: top; } + .output-doc .badge { display: inline-block; padding: 1px 8px; border-radius: 3px; font-family: var(--mono); font-size: 10px; font-weight: 600; } + .badge-R { background: var(--accent-green-dim); color: var(--accent-green); } + .badge-O { background: var(--accent-amber-dim); color: var(--accent-amber); } + .badge-NA { background: var(--surface2); color: var(--text-dim); } + .badge-Y { background: var(--accent-green-dim); color: var(--accent-green); } + .badge-N { background: var(--red-dim); color: var(--red); } + + .info-kv { display: flex; gap: 8px; font-size: 12px; margin-bottom: 6px; } + .info-kv .k { font-family: var(--mono); font-size: 10px; color: var(--text-muted); white-space: nowrap; min-width: 130px; } + .info-kv .v { color: var(--text); } + + /* ── LOADING ── */ + .loading-overlay { + display: none; position: fixed; inset: 0; background: rgba(244,245,247,.82); z-index: 200; + align-items: center; justify-content: center; flex-direction: column; gap: 16px; backdrop-filter: blur(2px); + } + .loading-overlay.active { display: flex; } + .spinner { width: 36px; height: 36px; border: 2px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin .7s linear infinite; } + @keyframes spin { to { transform: rotate(360deg); } } + .loading-text { font-family: var(--mono); font-size: 11px; color: var(--text-muted); letter-spacing: .1em; } + + /* ── VALIDATION ── */ + .step-tab.invalid { color: var(--red); background: var(--red-dim); border-bottom-color: var(--red); } + .step-tab.invalid.active { color: var(--red); background: var(--red-dim); border-bottom-color: var(--red); } + .step-tab .step-flag { display: none; } + .step-tab.invalid .step-flag { display: inline; margin-left: 4px; } + input.input-invalid, textarea.input-invalid, select.input-invalid { border-color: var(--red); box-shadow: 0 0 0 3px var(--red-dim); } + .radio-group.group-invalid, .check-group.group-invalid { outline: 1px solid var(--red); outline-offset: 4px; border-radius: var(--radius); } + .req-hint { font-family: var(--mono); font-size: 10px; color: var(--red); margin-top: 4px; display: none; } + .req-hint.show { display: block; } + + /* ── KEY ROLES ── */ + .roles-list { display: flex; flex-direction: column; gap: 10px; margin-bottom: 12px; } + .role-row { display: grid; grid-template-columns: 220px 1fr 36px; gap: 10px; align-items: center; } + .role-row .role-other { display: none; } + .role-row.is-other { grid-template-columns: 220px 1fr 1fr 36px; } + .role-row.is-other .role-other { display: block; } + .row-del { + width: 34px; height: 34px; border-radius: var(--radius); border: 1px solid var(--border-strong); + background: var(--surface); color: var(--text-dim); cursor: pointer; font-size: 16px; line-height: 1; + display: flex; align-items: center; justify-content: center; transition: all .12s; flex-shrink: 0; + } + .row-del:hover { border-color: var(--red); color: var(--red); background: var(--red-dim); } + .add-btn { + display: inline-flex; align-items: center; gap: 7px; padding: 8px 16px; border-radius: var(--radius); + border: 1px dashed var(--border-strong); background: var(--surface); color: var(--text-muted); + font-family: var(--mono); font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; + cursor: pointer; transition: all .15s; + } + .add-btn:hover { border-color: var(--accent); color: var(--accent); border-style: solid; background: var(--accent-dim); } + + /* ── WO TYPE NAME EDITING ── */ + td.wo-name-cell { padding: 4px; background: var(--surface2); } + td.wo-name-cell input { font-family: var(--mono); font-size: 11px; color: var(--text); font-weight: 500; } + .wo-custom-tag { font-family: var(--mono); font-size: 8px; color: var(--accent); letter-spacing: .1em; margin-left: 6px; vertical-align: middle; } + + /* ── SEQUENCE BUILDER ── */ + .seq-builder { margin-bottom: 18px; } + .seq-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; } + .seq-step { + display: flex; align-items: center; gap: 12px; padding: 11px 14px; + background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--radius); + box-shadow: var(--shadow); transition: border-color .12s, box-shadow .12s, opacity .12s; + } + .seq-step:hover { border-color: var(--accent); } + .seq-step.dragging { opacity: .35; border-style: dashed; } + .seq-step.drag-over { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); } + .seq-handle { cursor: grab; color: var(--text-dim); font-size: 16px; line-height: 1; user-select: none; flex-shrink: 0; } + .seq-handle:active { cursor: grabbing; } + .seq-num { + width: 24px; height: 24px; flex-shrink: 0; border-radius: 50%; background: var(--accent-dim); color: var(--accent); + font-family: var(--mono); font-size: 11px; font-weight: 600; display: flex; align-items: center; justify-content: center; + } + .seq-step input.seq-label { + border: 1px solid transparent; background: transparent; font-size: 14px; padding: 5px 8px; color: var(--text); flex: 1; + } + .seq-step input.seq-label:focus { background: var(--surface2); border-color: var(--accent); box-shadow: none; } + .seq-arrow { text-align: center; color: var(--text-dim); font-size: 13px; line-height: .4; margin: -2px 0; } + .seq-add-row { display: flex; gap: 10px; align-items: center; } + .seq-add-row input { flex: 1; } + .seq-step.gate { border-color: var(--accent-amber); background: var(--accent-amber-dim); border-style: dashed; } + .seq-step.gate .seq-label { color: var(--accent-amber); font-weight: 500; } + .seq-gate-badge { + flex-shrink: 0; padding: 3px 9px; border-radius: 20px; background: var(--accent-amber); color: #fff; + font-family: var(--mono); font-size: 9px; font-weight: 600; letter-spacing: .08em; white-space: nowrap; + } + .add-btn-gate { border-color: var(--accent-amber); color: var(--accent-amber); } + .add-btn-gate:hover { border-color: var(--accent-amber); color: var(--accent-amber); background: var(--accent-amber-dim); border-style: solid; } + .field-hint { font-family: var(--mono); font-size: 10px; color: var(--text-dim); margin-top: 5px; line-height: 1.5; } + .field-hint code { background: var(--surface2); padding: 1px 6px; border-radius: 3px; color: var(--accent); } + .field-hint a { color: var(--accent); text-decoration: none; margin-left: 6px; font-weight: 600; } + .field-hint a:hover { text-decoration: underline; } + .other-field { margin-top: 6px; } + + /* ── REVIEW COMMENTS ─────────────────────────────────────────────── */ + .cbadge-total { display:inline-block; min-width:16px; padding:0 5px; margin-left:4px; font-family:var(--mono); + font-size:10px; font-weight:700; line-height:16px; text-align:center; color:#fff; background:var(--accent); border-radius:9px; } + .step-tab { position:relative; } + .step-tab .cbadge { position:absolute; top:4px; right:4px; min-width:15px; height:15px; padding:0 4px; + font-family:var(--mono); font-size:9px; font-weight:700; line-height:15px; text-align:center; + color:#fff; background:var(--accent-amber); border-radius:8px; box-shadow:0 0 0 2px var(--surface); } + + .cmt-overlay { position:fixed; inset:0; background:rgba(20,30,50,.28); opacity:0; pointer-events:none; + transition:opacity .2s ease; z-index:60; } + .cmt-overlay.open { opacity:1; pointer-events:auto; } + .cmt-drawer { position:fixed; top:0; right:0; height:100vh; width:380px; max-width:92vw; background:var(--surface); + border-left:1px solid var(--border); box-shadow:var(--shadow-lg); transform:translateX(100%); + transition:transform .24s ease; z-index:61; display:flex; flex-direction:column; } + .cmt-drawer.open { transform:translateX(0); } + .cmt-head { display:flex; align-items:center; justify-content:space-between; padding:16px 18px; + border-bottom:1px solid var(--border); } + .cmt-title { font-weight:700; font-size:14px; color:var(--text); } + .cmt-x { background:none; border:none; color:var(--text-muted); font-size:15px; cursor:pointer; padding:4px 8px; border-radius:4px; } + .cmt-x:hover { background:var(--surface2); color:var(--text); } + .cmt-namebar { padding:12px 18px; border-bottom:1px solid var(--border); } + .cmt-namebar label { display:block; font-size:10px; text-transform:uppercase; letter-spacing:.04em; color:var(--text-muted); margin-bottom:5px; } + .cmt-namebar input { width:100%; padding:8px 10px; border:1px solid var(--border-strong); border-radius:var(--radius); font-family:var(--sans); font-size:13px; color:var(--text); background:var(--surface); } + .cmt-compose { padding:14px 18px; border-bottom:1px solid var(--border); background:var(--surface2); } + .cmt-compose-label { font-size:11px; color:var(--text-muted); margin-bottom:7px; } + .cmt-compose-label strong { color:var(--accent); } + .cmt-compose textarea { width:100%; padding:9px 11px; border:1px solid var(--border-strong); border-radius:var(--radius); + font-family:var(--sans); font-size:13px; color:var(--text); resize:vertical; background:var(--surface); } + .cmt-add { width:100%; margin-top:9px; } + .cmt-list { flex:1; overflow-y:auto; padding:10px 14px; } + .cmt-empty { text-align:center; color:var(--text-dim); font-size:12px; padding:36px 12px; } + .cmt-item { border:1px solid var(--border); border-radius:var(--radius); padding:10px 12px; margin-bottom:9px; background:var(--surface); } + .cmt-item.mine { border-color:var(--accent); background:var(--accent-dim); } + .cmt-meta { display:flex; align-items:center; gap:7px; margin-bottom:5px; font-size:11px; } + .cmt-author { font-weight:700; color:var(--text); } + .cmt-step { font-family:var(--mono); font-size:9px; text-transform:uppercase; letter-spacing:.03em; + color:var(--accent); background:var(--accent-dim); padding:1px 6px; border-radius:9px; } + .cmt-time { color:var(--text-dim); margin-left:auto; font-size:10px; } + .cmt-text { font-size:13px; color:var(--text); line-height:1.5; white-space:pre-wrap; word-break:break-word; } + .cmt-del { background:none; border:none; color:var(--text-dim); cursor:pointer; font-size:11px; padding:2px 5px; border-radius:4px; } + .cmt-del:hover { background:var(--red-dim); color:var(--red); } + .cmt-jump { background:none; border:none; color:var(--accent); cursor:pointer; font-size:10px; padding:0; margin-top:4px; } + .cmt-jump:hover { text-decoration:underline; } + .cmt-foot { border-top:1px solid var(--border); padding:12px 18px; } + .cmt-note { font-size:10px; color:var(--text-muted); line-height:1.5; margin-bottom:10px; } + .cmt-foot-btns { display:flex; gap:7px; flex-wrap:wrap; } + .cmt-foot-btns .btn { padding:6px 11px; font-size:11px; } + .cmt-clear { margin-left:auto; color:var(--text-muted); } + + /* ── REV1: validation, governance, summary ───────────────────────── */ + .radio-group.group-invalid, .check-group.group-invalid { outline:2px solid var(--red); outline-offset:4px; border-radius:var(--radius); } + .ov-select.ov-unset { color:var(--red) !important; border-color:var(--red); } + .use-btn { display:inline-block; margin-left:8px; padding:4px 14px; font-family:var(--sans); font-size:11px; font-weight:700; + color:#fff; background:var(--accent-green); border:none; border-radius:var(--radius); cursor:pointer; letter-spacing:.03em; } + .use-btn:hover { background:#0f7a40; } + .sum-chips { display:flex; flex-wrap:wrap; gap:7px; } + .sum-chip { background:var(--accent-dim); color:var(--accent); border:1px solid #b9d2fb; border-radius:3px; + padding:3px 10px; font-family:var(--mono); font-size:10px; } + .sum-warn { margin-top:10px; color:var(--accent-amber); background:var(--accent-amber-dim); border:1px solid #f0d9ad; + border-radius:var(--radius); padding:7px 10px; font-size:11px; } + + /* ── CREATION TOOL ───────────────────────────────────────────────── */ + .ctx-bar { max-width:1080px; margin:0 auto; padding:12px 28px; display:flex; align-items:center; gap:20px; + border-bottom:1px solid var(--border); background:var(--surface); flex-wrap:wrap; } + .ctx-empty { color:var(--text-muted); font-size:13px; } + .ctx-main .ctx-proj { font-weight:700; color:var(--text); font-size:14px; } + .ctx-main .ctx-sub { font-size:11px; color:var(--text-muted); margin-top:2px; } + .ctx-sample { font-family:var(--mono); font-size:9px; font-weight:700; color:var(--accent-amber); + background:var(--accent-amber-dim); border:1px solid #f0d9ad; border-radius:9px; padding:1px 7px; margin-left:6px; vertical-align:middle; } + .ctx-meta { margin-left:auto; display:flex; gap:16px; font-size:11px; color:var(--text-muted); flex-wrap:wrap; } + .ctx-meta b { color:var(--accent); } + .ctx-meta code { background:var(--surface2); padding:1px 6px; border-radius:3px; color:var(--accent); } + .link-btn { background:none; border:none; color:var(--accent); cursor:pointer; font-size:inherit; padding:0; text-decoration:underline; } + + .mode-wrap { max-width:1080px; margin:0 auto; padding:16px 28px 0; display:flex; align-items:center; gap:16px; } + .mode-toggle { display:inline-flex; border:1px solid var(--border-strong); border-radius:6px; overflow:hidden; } + .mode-btn { padding:8px 18px; font-family:var(--sans); font-size:13px; font-weight:600; border:none; background:var(--surface); + color:var(--text-muted); cursor:pointer; } + .mode-btn.active { background:var(--accent); color:#fff; } + .created-count { font-size:11px; color:var(--text-muted); } + + .wo-section { border:1px solid var(--border); border-radius:var(--radius); padding:11px 13px; margin-bottom:9px; background:var(--surface); } + .wo-section-head { display:flex; align-items:center; gap:9px; margin-bottom:7px; flex-wrap:wrap; } + .wo-section-name { font-weight:700; font-size:13px; color:var(--text); } + .wo-section-src { font-family:var(--mono); font-size:10px; color:var(--text-muted); margin-left:auto; } + .wo-section textarea { width:100%; padding:8px 10px; border:1px solid var(--border-strong); border-radius:var(--radius); + font-family:var(--sans); font-size:13px; color:var(--text); resize:vertical; background:var(--surface); } + .empty-hint { color:var(--text-dim); font-size:13px; padding:18px 6px; text-align:center; } + + .crew-row { grid-template-columns:1fr 36px !important; } + .crew-row.is-other { grid-template-columns:1fr 1fr 70px 36px !important; } + .crew-row:not(.is-other) { grid-template-columns:1fr 70px 36px !important; } + + .batch-head { display:flex; align-items:center; gap:16px; margin:14px 0 10px; font-size:13px; flex-wrap:wrap; } + #batch-preview table input { width:100%; } + + /* ── WORK PACKAGE FORM ───────────────────────────────────────────── */ + .sop-hint { color:var(--accent) !important; } + .release-banner { max-width:1080px; margin:0 auto; padding:0 28px; } + .release-banner .rb-inner { margin-top:14px; border-radius:var(--radius); padding:11px 16px; font-size:13px; font-weight:600; + display:flex; align-items:center; gap:10px; } + .rb-ready { background:var(--accent-green-dim); color:var(--accent-green); border:1px solid #b6e3c6; } + .rb-notready { background:var(--accent-amber-dim); color:var(--accent-amber); border:1px solid #f0d9ad; } + .rb-hold { background:var(--red-dim); color:var(--red); border:1px solid #f3c4c4; } + .pill-hold.selected { background:var(--red) !important; border-color:var(--red) !important; } + .pill-hold.selected .dot { background:#fff !important; } + + .cstatus { display:inline-flex; border:1px solid var(--border-strong); border-radius:5px; overflow:hidden; } + .cstatus button { border:none; background:var(--surface); color:var(--text-muted); font-family:var(--sans); font-size:11px; + font-weight:600; padding:4px 10px; cursor:pointer; border-right:1px solid var(--border); } + .cstatus button:last-child { border-right:none; } + .cstatus button.on-open { background:var(--red); color:#fff; } + .cstatus button.on-cleared { background:var(--accent-green); color:#fff; } + .cstatus button.on-na { background:var(--text-muted); color:#fff; } + #material-body input, #attach-body input, #constraint-body input, #signoff-body input { width:100%; } + .signoff-check { width:18px; height:18px; cursor:pointer; } + + /* ── COMMENT-DRIVEN ADDITIONS ────────────────────────────────────── */ + .locked-field { background:var(--surface2)!important; color:var(--text-muted); cursor:not-allowed; } + .lock-row { display:flex; align-items:center; gap:10px; margin-top:5px; } + .lock-edit { background:none; border:none; color:var(--accent); cursor:pointer; font-size:11px; padding:0; } + .override-note { font-size:11px; color:var(--accent-amber); } + .sop-tag { font-size:9px; font-weight:700; color:var(--accent); background:var(--accent-dim,#eaf0fd); border:1px solid #cdd9f2; border-radius:9px; padding:1px 6px; margin-left:6px; vertical-align:middle; } + + #toast { position:fixed; bottom:26px; left:50%; transform:translateX(-50%) translateY(20px); background:var(--text); color:#fff; + padding:10px 20px; border-radius:8px; font-size:13px; font-weight:600; opacity:0; pointer-events:none; transition:all .25s; z-index:9999; box-shadow:0 6px 24px rgba(0,0,0,.25); } + #toast.show { opacity:1; transform:translateX(-50%) translateY(0); } + + .modal-overlay { position:fixed; inset:0; background:rgba(20,28,40,.55); display:none; align-items:center; justify-content:center; z-index:9000; padding:20px; } + .modal-overlay.open { display:flex; } + .modal { background:var(--surface); border-radius:12px; width:100%; max-width:520px; box-shadow:0 20px 60px rgba(0,0,0,.3); overflow:hidden; max-height:90vh; display:flex; flex-direction:column; } + .modal-head { display:flex; align-items:center; justify-content:space-between; padding:16px 20px; border-bottom:1px solid var(--border); } + .modal-title { font-weight:700; font-size:15px; color:var(--text); } + .modal-body { padding:18px 20px; overflow-y:auto; } + .modal-body .field { margin-bottom:14px; } + .modal-foot { display:flex; justify-content:flex-end; gap:10px; padding:14px 20px; border-top:1px solid var(--border); } + .hold-photo-preview img { max-width:160px; max-height:120px; border-radius:6px; border:1px solid var(--border); margin-top:8px; display:block; } + + /* ── REV 2 ADDITIONS ─────────────────────────────────────────────── */ + .auto-tag { font-size:9px; font-weight:700; color:var(--accent-green); background:var(--accent-green-dim); border:1px solid #b6e3c6; border-radius:9px; padding:1px 6px; margin-left:6px; vertical-align:middle; } + .derived-box { padding:9px 11px; border:1px dashed var(--border-strong); border-radius:var(--radius); background:var(--surface2); color:var(--text); font-size:13px; font-weight:600; min-height:38px; display:flex; align-items:center; } + .material-actions { display:flex; gap:10px; flex-wrap:wrap; margin-top:8px; } + .workstep-row { display:flex; align-items:flex-start; gap:10px; margin-bottom:8px; } + .workstep-row .ws-num { flex:0 0 26px; height:26px; border-radius:50%; background:var(--accent); color:#fff; font-size:12px; font-weight:700; display:flex; align-items:center; justify-content:center; margin-top:5px; } + .workstep-row textarea { flex:1; padding:8px 10px; border:1px solid var(--border-strong); border-radius:var(--radius); font-family:var(--sans); font-size:13px; resize:vertical; min-height:38px; } + .workstep-row .row-del { flex:0 0 auto; margin-top:6px; } + + /* ── MOBILE (comment 5) ──────────────────────────────────────────── */ + @media (max-width: 720px) { + .header { flex-wrap:wrap; gap:6px; padding:10px 14px; } + .header-title { width:100%; order:5; } + .header .btn { margin-left:0 !important; padding:6px 10px !important; font-size:12px; } + .header .btn:first-of-type { margin-left:auto !important; } + .main { padding:14px !important; } + .ctx-bar, .release-banner { padding-left:14px; padding-right:14px; } + .ctx-meta { margin-left:0; width:100%; } + .field-grid { grid-template-columns:1fr !important; } + .radio-group { flex-wrap:wrap; } + .card { padding:16px 14px !important; } + .modal { max-width:100% !important; } + .output-toolbar { flex-wrap:wrap; } + .material-actions .add-btn { flex:1 1 auto; } + table { min-width:520px; } /* keep columns legible; .table-wrap scrolls */ + .cstatus button { padding:6px 8px; } + } + @media (max-width: 480px) { + .header-logo { font-size:14px; } + .workstep-row textarea { font-size:16px; } /* avoid iOS zoom */ + input, select, textarea { font-size:16px; } /* avoid iOS zoom on focus */ + } + + /* ── REV 3 ADDITIONS ─────────────────────────────────────────────── */ + /* SOP-inherited field highlight (comment 8) */ + .sop-inherited { background:rgba(37,99,214,0.07) !important; border-color:var(--accent) !important; opacity:0.85; color:var(--text); } + .sop-inherited:focus { opacity:1; } + + /* Dev mode (comment 7) */ + .logo-wrap { position:relative; display:flex; align-items:center; } + .dev-toggle { position:absolute; left:2px; bottom:-9px; width:18px; height:7px; padding:0; border:none; + background:var(--text-dim); opacity:0.10; border-radius:3px; cursor:pointer; } + .dev-toggle:hover { opacity:0.35; } + .dev-banner { background:#3a2a00; color:#ffd479; font-size:12.5px; font-weight:700; text-align:center; padding:7px 14px; letter-spacing:.3px; } + body.dev-mode .header { box-shadow: inset 0 -3px 0 #ffb000; } + + /* SOP reference links (comments 1,3,4) */ + .sop-ref-links { margin-bottom:12px; } + .ref-links-title { font-size:12px; color:var(--text-muted); margin-bottom:6px; } + .ref-links { display:flex; flex-wrap:wrap; gap:8px; } + .ref-link { display:inline-flex; align-items:center; gap:6px; font-size:12.5px; font-weight:600; color:var(--accent); + background:var(--accent-dim,#eef3fd); border:1px solid #cdd9f2; border-radius:8px; padding:6px 11px; text-decoration:none; } + .ref-link:hover { background:#e2ecfc; } + .ref-link .ref-sys { font-weight:400; color:var(--text-muted); font-size:11px; } + .ref-link-sm { font-size:12px; font-weight:600; color:var(--accent); text-decoration:none; } + .ref-link-sm:hover { text-decoration:underline; } + + /* Sign-off date override (comment 5) */ + .so-date { font-size:13px; font-variant-numeric:tabular-nums; } + .so-ovr { margin-left:8px; font-size:11px; }