Add vs PY % delta column to monthly dept breakdown table

Shows (actual_mtd - PY_MTD) / PY_MTD for current month rows,
(actual - PY_full) / PY_full for past months. Red if wages up vs PY,
green if down. Total row uses same basis. Matches department by ID
against pyDepts so departments with no PY data show —.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-23 12:44:17 +00:00
parent 5b243b3ef8
commit 27f9190dc5

View file

@ -156,6 +156,7 @@ export default function Monthly() {
const totalActual = deptSummary.reduce((s, d) => s + d.actual_mtd, 0) const totalActual = deptSummary.reduce((s, d) => s + d.actual_mtd, 0)
const totalForecast = deptSummary.reduce((s, d) => s + d.forecast_eom, 0) const totalForecast = deptSummary.reduce((s, d) => s + d.forecast_eom, 0)
const pyTotalWages = pyDepts.reduce((s, d) => s + d.cost, 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 // MTD row: pro-rata budget to yesterday
const yDay = isCurrentMonth ? parseInt(yesterdayStr.slice(8)) : dim const yDay = isCurrentMonth ? parseInt(yesterdayStr.slice(8)) : dim
@ -349,6 +350,7 @@ export default function Monthly() {
<th className="right">{isCurrentMonth ? 'Actual MTD' : 'Actual'}</th> <th className="right">{isCurrentMonth ? 'Actual MTD' : 'Actual'}</th>
{isCurrentMonth && <th className="right">Forecast EOM</th>} {isCurrentMonth && <th className="right">Forecast EOM</th>}
<th className="right">% of Total</th> <th className="right">% of Total</th>
<th className="right">vs PY</th>
<th className="right">Budget</th> <th className="right">Budget</th>
<th className="right">% Budget</th> <th className="right">% Budget</th>
<th className="right">Variance</th> <th className="right">Variance</th>
@ -364,6 +366,10 @@ export default function Monthly() {
const dp = depBudg != null && depBudg > 0 ? (displayCost / depBudg) * 100 : null const dp = depBudg != null && depBudg > 0 ? (displayCost / depBudg) * 100 : null
const dv = depBudg != null ? displayCost - depBudg : null const dv = depBudg != null ? displayCost - depBudg : null
const depOfTotal = totalDisplay > 0 ? (displayCost / totalDisplay) * 100 : 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 ( return (
<tr key={dep.department_id} style={{ cursor: 'pointer' }} <tr key={dep.department_id} style={{ cursor: 'pointer' }}
onClick={() => setModal({ deptId: dep.department_id, deptName: dep.department_name })}> onClick={() => setModal({ deptId: dep.department_id, deptName: dep.department_name })}>
@ -376,6 +382,11 @@ export default function Monthly() {
<td className="right" style={{ color: 'var(--text-muted)' }}> <td className="right" style={{ color: 'var(--text-muted)' }}>
{depOfTotal != null ? `${depOfTotal.toFixed(1)}%` : '—'} {depOfTotal != null ? `${depOfTotal.toFixed(1)}%` : '—'}
</td> </td>
<td className="right">
{pyDelta != null
? <span className={pyDelta > 0 ? 'variance-over' : 'variance-under'}>{pyDelta > 0 ? '+' : ''}{pyDelta.toFixed(1)}%</span>
: '—'}
</td>
<td className="right">{depBudg != null ? fmtMoney(depBudg) : '—'}</td> <td className="right">{depBudg != null ? fmtMoney(depBudg) : '—'}</td>
<td className="right"> <td className="right">
{dp != null ? <span className={`pct-badge ${pctClass(dp)}`}>{fmtDelta(dp)}</span> : '—'} {dp != null ? <span className={`pct-badge ${pctClass(dp)}`}>{fmtDelta(dp)}</span> : '—'}
@ -391,6 +402,14 @@ export default function Monthly() {
<td className="right">{fmtMoney(totalActual)}</td> <td className="right">{fmtMoney(totalActual)}</td>
{isCurrentMonth && <td className="right">{fmtMoney(totalForecast)}</td>} {isCurrentMonth && <td className="right">{fmtMoney(totalForecast)}</td>}
<td className="right">100%</td> <td className="right">100%</td>
<td className="right">
{(() => {
const pyBase = isCurrentMonth ? pyTotalWages : pyTotalWagesFull
if (pyBase <= 0) return '—'
const d = ((totalActual - pyBase) / pyBase) * 100
return <span className={d > 0 ? 'variance-over' : 'variance-under'}>{d > 0 ? '+' : ''}{d.toFixed(1)}%</span>
})()}
</td>
<td className="right">{budget != null ? fmtMoney(budget) : '—'}</td> <td className="right">{budget != null ? fmtMoney(budget) : '—'}</td>
<td className="right"> <td className="right">
{pctBudgFull != null ? <span className={`pct-badge ${pctClass(pctBudgFull)}`}>{fmtDelta(pctBudgFull)}</span> : '—'} {pctBudgFull != null ? <span className={`pct-badge ${pctClass(pctBudgFull)}`}>{fmtDelta(pctBudgFull)}</span> : '—'}