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:
parent
5b243b3ef8
commit
27f9190dc5
1 changed files with 22 additions and 3 deletions
|
|
@ -156,6 +156,7 @@ export default function Monthly() {
|
|||
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() {
|
|||
<th className="right">{isCurrentMonth ? 'Actual MTD' : 'Actual'}</th>
|
||||
{isCurrentMonth && <th className="right">Forecast → EOM</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">Variance</th>
|
||||
|
|
@ -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 (
|
||||
<tr key={dep.department_id} style={{ cursor: 'pointer' }}
|
||||
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)' }}>
|
||||
{depOfTotal != null ? `${depOfTotal.toFixed(1)}%` : '—'}
|
||||
</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">
|
||||
{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>
|
||||
{isCurrentMonth && <td className="right">{fmtMoney(totalForecast)}</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">
|
||||
{pctBudgFull != null ? <span className={`pct-badge ${pctClass(pctBudgFull)}`}>{fmtDelta(pctBudgFull)}</span> : '—'}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue