PM Toolkit

PM Tools /* Root styling */ body { font-family: Arial, sans-serif; background: #ffffff; margin: 0; padding: 10px; } .pm-tools { font-family: Arial, sans-serif; font-size: 12px; } /* GRID — Row 1: 3 widgets; Row 2: Estimator + Schedule */ .pm-tools .tools-grid { display: grid; grid-template-columns: repeat(3, minmax(220px, 1fr)); gap: 14px; padding: 10px; } /* Generic tool box */ .pm-tools .tool-box { background: #F8FCFF; border: 1px solid #9BD5EF; border-radius: 10px; padding: 0; display: flex; flex-direction: column; } /* Headers */ .pm-tools .tool-header { background: #9BD5EF; padding: 6px 8px; display: flex; align-items: center; font-size: 12px; font-weight: bold; color: #1E3A5F; border-radius: 10px 10px 0 0; } .pm-tools .tool-header .icon { font-size: 15px; margin-right: 6px; } /* Body + inputs */ .pm-tools .tool-body { padding: 8px; } .pm-tools .tool-body input { width: 100%; padding: 4px; margin-bottom: 6px; font-size: 11px; border: 1px solid #9BD5EF; border-radius: 6px; outline: none; box-sizing: border-box; } /* Buttons */ .pm-tools button { background: #9BD5EF; border: none; padding: 4px 10px; font-size: 11px; border-radius: 6px; cursor: pointer; color: #1E3A5F; font-weight: bold; } .pm-tools button:hover { background: #78c3e2; } .pm-tools .button-row { text-align: right; } /* Toggle */ .switch { position: relative; display: inline-block; width: 40px; height: 18px; } .switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top:0; left:0; right:0; bottom:0; background:#d6d6d6; transition:.2s; border-radius:18px; } .slider:before { position:absolute; content:””; height:14px; width:14px; left:2px; bottom:2px; background:#fff; transition:.2s; border-radius:50%; } .switch input:checked + .slider { background:#9BD5EF; } .switch input:checked + .slider:before { transform:translateX(20px); } .pm-tools .warning { color:red; font-size:11px; } .pm-tools .result { margin-top:5px; font-size:11px; } /* Phase rows inside Effort Estimator */ .pm-tools .phase-row { display:flex; justify-content:space-between; align-items:center; margin-bottom:3px; } .pm-tools .phase-row span { font-size:11px; } .pm-tools .phase-row input { width:60px; margin-bottom:0; } /* Schedule Table */ .pm-tools .schedule-table { margin-top: 8px; } .pm-tools table.schedule { border-collapse: collapse; font-size: 11px; width: auto; /* width grows based on weeks */ } .pm-tools table.schedule th, .pm-tools table.schedule td { border: 1px solid #9BD5EF; padding: 4px; text-align: center; } .pm-tools .filled { background: #9BD5EF; } /* Layout assignments */ .pm-tools .tool-box.estimator-box { grid-column: 1; } .pm-tools .tool-box.schedule-box { grid-column: 2 / span 2; } @media (max-width: 800px) { .pm-tools .tools-grid { grid-template-columns:1fr; } .pm-tools .tool-box.estimator-box, .pm-tools .tool-box.schedule-box { grid-column:1; } }
🧮GM Calculator
Revenue: Cost: Contingency %:
📈Revenue Projection
Cost: Contingency %: GM %:
💰Costing Calculator
Revenue: GM %:
Effort Estimator
Units (off = Hours, on = PD):
Dev + UT Effort (Y): Dev+UT as % of Total: 1 PD = hours
Phase Percentages (excluding Dev+UT)
Requirements %
Design %
Testing %
UAT %
Hypercare %
📅Schedule Generator
Developers: Dev Boost %:
Testers: Tester Boost %:
window.pmEffort = null; window.pmSchedule = null; let isPD = false; /* Toggle Hours/PD */ function onToggleUnit() { const hpd = +hoursPerDay.value || 8; const val = +devValue.value; if (!val) { isPD = unitToggle.checked; return; } if (!isPD && unitToggle.checked) { devValue.value = (val / hpd).toFixed(2); isPD = true; } else if (isPD && !unitToggle.checked) { devValue.value = (val * hpd).toFixed(1); isPD = false; } if (effortResult.innerHTML.trim() !== “”) { effCalc(); } } /* GM calc */ function gmCalc(){ const rev = +gmRevenue.value; const cost = +gmCost.value; const contPct = (+gmCont.value || 0) / 100; if (!rev || !cost) { gmResult.innerHTML = “Enter Revenue & Cost”; return; } const finalCost = cost * (1 + contPct); const gm = ((rev – finalCost) / rev) * 100; gmResult.innerHTML = “Final Cost: ” + finalCost.toFixed(2) + “
GM: ” + gm.toFixed(2) + “%”; } /* Revenue projection */ function revCalc(){ const cost = +revCost.value; const contPct = (+revCont.value || 0) / 100; const gmPct = +revGM.value; if (!cost || gmPct = 100) { revResult.innerHTML = “Invalid inputs”; return; } const finalCost = cost * (1 + contPct); const gm = gmPct / 100; const rev = finalCost / (1 – gm); revResult.innerHTML = “Final Cost: ” + finalCost.toFixed(2) + “
Revenue: ” + rev.toFixed(2); } /* Cost reverse calc */ function costCalc(){ const rev = +costRevenue.value; const gmPct = +costGM.value; if (!rev || gmPct = 100) { costResult.innerHTML = “Invalid inputs”; return; } const max = rev * (1 – gmPct / 100); costResult.innerHTML = “Max Cost: ” + max.toFixed(2); } /* Effort estimator with phase % auto-balance */ function effCalc(){ const Y = +devValue.value; const xPct = +devPct.value; const x = xPct / 100; const hpd = +hoursPerDay.value || 8; let reqP = +pReq.value || 0; let desP = +pDes.value || 0; let testP = +pTest.value || 0; let uatP = +pUAT.value || 0; let hcP = +pHC.value || 0; effortResult.innerHTML = “”; pctWarn.innerHTML = “”; if (!Y || !x || x = 1) { effortResult.innerHTML = “Enter valid Dev+UT effort and Dev% (0–100).”; return; } const remainingAllowed = 100 – xPct; if (remainingAllowed 0) { if (remainingSum 0.01) { const scale = remainingAllowed / remainingSum; reqP *= scale; desP *= scale; testP *= scale; uatP *= scale; hcP *= scale; autoAdjusted = true; } } else { // remainingAllowed = 0 → all effort is Dev reqP = desP = testP = uatP = hcP = 0; autoAdjusted = true; } if (autoAdjusted) { pReq.value = reqP.toFixed(1); pDes.value = desP.toFixed(1); pTest.value = testP.toFixed(1); pUAT.value = uatP.toFixed(1); pHC.value = hcP.toFixed(1); pctWarn.innerHTML = “Phase % auto-balanced to ” + remainingAllowed.toFixed(1) + “% (remaining after Dev).”; } const hrsDevPlus = isPD ? Y * hpd : Y; const totalHrs = hrsDevPlus / x; const hrsReq = totalHrs * (reqP / 100); const hrsDes = totalHrs * (desP / 100); const hrsTest = totalHrs * (testP / 100); const hrsUAT = totalHrs * (uatP / 100); const hrsHC = totalHrs * (hcP / 100); const hrsDev = totalHrs * x; // dev portion const daysReq = hrsReq / hpd; const daysDes = hrsDes / hpd; const daysDev = hrsDev / hpd; const daysTest = hrsTest / hpd; const daysUAT = hrsUAT / hpd; const daysHC = hrsHC / hpd; window.pmEffort = { req: daysReq, des: daysDes, dev: daysDev, test: daysTest, uat: daysUAT, hc: daysHC }; function fmt(h){ return isPD ? (h / hpd).toFixed(2) + ” PD” : h.toFixed(1) + ” hrs”; } effortResult.innerHTML = “Total Effort: ” + fmt(totalHrs) + “
” + “Requirements: ” + fmt(hrsReq) + “
” + “Design: ” + fmt(hrsDes) + “
” + “Development: ” + fmt(hrsDev) + “
” + “Testing: ” + fmt(hrsTest) + “
” + “UAT: ” + fmt(hrsUAT) + “
” + “Hypercare: ” + fmt(hrsHC); scheduleGrid.innerHTML = “”; totalWeeksLabel.innerHTML = “”; window.pmSchedule = null; } /* Schedule generator with Dev & Tester boosts */ function generateSchedule(){ if (!window.pmEffort) { scheduleGrid.innerHTML = “Run Effort Estimator first.”; return; } const d = window.pmEffort; // FIX: always use getElementById const devCnt = Math.max(1, +(document.getElementById(“devCount”).value) || 1); const devBoostPct = +(document.getElementById(“devBoost”).value) || 0; const devBoost = devBoostPct / 100; const testerCnt = Math.max(1, +(document.getElementById(“testerCount”).value) || 1); const testerBoostPct = +(document.getElementById(“testerBoost”).value) || 0; const testerBoost = testerBoostPct / 100; const reqDays0 = d.req || 0; const desDays0 = d.des || 0; const devDays0 = d.dev || 0; const testDays0 = d.test || 0; const uatDays0 = d.uat || 0; const hcDays0 = d.hc || 0; // Apply developer boost to Design & Dev const devFactor = 1 + (devCnt – 1) * devBoost; const adjDes = devFactor > 0 ? desDays0 / devFactor : desDays0; const adjDev = devFactor > 0 ? devDays0 / devFactor : devDays0; // Apply tester boost to Testing const testFactor = 1 + (testerCnt – 1) * testerBoost; const adjTest = testFactor > 0 ? testDays0 / testFactor : testDays0; const totalDays = reqDays0 + adjDes + adjDev + adjTest + uatDays0 + hcDays0; const weeks = Math.max(1, Math.ceil(totalDays / 5)); totalWeeksLabel.innerHTML = “Total Project Weeks: ” + weeks; const phases = [ [“Requirements”, reqDays0], [“Design”, adjDes], [“Development”, adjDev], [“Testing”, adjTest], [“UAT”, uatDays0], [“Hypercare”, hcDays0] ]; let html = ““; for (let i = 1; i <= weeks; i++) html += "“; html += ““; let start = 0; const sched = []; phases.forEach(([name, pd]) => { const cells = []; html += ““; for (let w = 0; w 0 && start wkStart; cells.push(fill ? 1 : 0); html += fill ? “” : ““; } html += ““; sched.push({ name, cells }); start += pd; }); html += “
PhaseW” + i + “
” + name + “
“; scheduleGrid.innerHTML = html; window.pmSchedule = { weeks, sched }; } /* Export CSV (schedule only) */ function exportCSV(){ if (!window.pmSchedule) { alert(“Generate schedule first.”); return; } const { weeks, sched } = window.pmSchedule; let csv = “Phase,” + […Array(weeks).keys()].map(i => “W” + (i + 1)).join(“,”) + “\n”; sched.forEach(r => { csv += r.name + “,” + r.cells.join(“,”) + “\n”; }); const blob = new Blob([csv], { type: “text/csv” }); const a = document.createElement(“a”); a.href = URL.createObjectURL(blob); a.download = “schedule.csv”; a.click(); }