diff --git a/frontend/src/pages/Monthly.tsx b/frontend/src/pages/Monthly.tsx index 709f646..f232613 100644 --- a/frontend/src/pages/Monthly.tsx +++ b/frontend/src/pages/Monthly.tsx @@ -153,9 +153,10 @@ export default function Monthly() { } }).sort((a, b) => b.forecast_eom - a.forecast_eom) - const totalActual = deptSummary.reduce((s, d) => s + d.actual_mtd, 0) - const totalForecast = deptSummary.reduce((s, d) => s + d.forecast_eom, 0) - const pyTotalWages = pyDepts.reduce((s, d) => s + d.cost, 0) + const totalActual = deptSummary.reduce((s, d) => s + d.actual_mtd, 0) + const totalForecast = deptSummary.reduce((s, d) => s + d.forecast_eom, 0) + const pyTotalWages = pyDepts.reduce((s, d) => s + d.cost, 0) + const pyTotalWagesFull = pyDepts.reduce((s, d) => s + d.costFull, 0) // MTD row: pro-rata budget to yesterday const yDay = isCurrentMonth ? parseInt(yesterdayStr.slice(8)) : dim @@ -349,6 +350,7 @@ export default function Monthly() { {isCurrentMonth ? 'Actual MTD' : 'Actual'} {isCurrentMonth && Forecast → EOM} % of Total + vs PY Budget % Budget Variance @@ -364,6 +366,10 @@ export default function Monthly() { const dp = depBudg != null && depBudg > 0 ? (displayCost / depBudg) * 100 : null const dv = depBudg != null ? displayCost - depBudg : null const depOfTotal = totalDisplay > 0 ? (displayCost / totalDisplay) * 100 : null + // PY: compare actual_mtd vs PY MTD for current month; actual vs PY full for past months + const pyDept = pyDepts.find(p => p.id === dep.department_id) + const pyCost = pyDept ? (isCurrentMonth ? pyDept.cost : pyDept.costFull) : null + const pyDelta = pyCost != null && pyCost > 0 ? ((dep.actual_mtd - pyCost) / pyCost) * 100 : null return ( setModal({ deptId: dep.department_id, deptName: dep.department_name })}> @@ -376,6 +382,11 @@ export default function Monthly() { {depOfTotal != null ? `${depOfTotal.toFixed(1)}%` : '—'} + + {pyDelta != null + ? 0 ? 'variance-over' : 'variance-under'}>{pyDelta > 0 ? '+' : ''}{pyDelta.toFixed(1)}% + : '—'} + {depBudg != null ? fmtMoney(depBudg) : '—'} {dp != null ? {fmtDelta(dp)} : '—'} @@ -391,6 +402,14 @@ export default function Monthly() { {fmtMoney(totalActual)} {isCurrentMonth && {fmtMoney(totalForecast)}} 100% + + {(() => { + const pyBase = isCurrentMonth ? pyTotalWages : pyTotalWagesFull + if (pyBase <= 0) return '—' + const d = ((totalActual - pyBase) / pyBase) * 100 + return 0 ? 'variance-over' : 'variance-under'}>{d > 0 ? '+' : ''}{d.toFixed(1)}% + })()} + {budget != null ? fmtMoney(budget) : '—'} {pctBudgFull != null ? {fmtDelta(pctBudgFull)} : '—'}