Add 5-tier warning thresholds to staff rota footer

Replaces the 2-row (vs Booked / vs with Pickup) footer with a progressive 5-row breakdown:
- Total Available (unchanged)
- vs Booked — plain diff, room cleaning only
- with Recurring Tasks — plain diff, adds recurring tasks
- with Pickup — plain diff, adds pickup hours
- with Adjustments — bold coloured final row with configurable thresholds

New 5-tier icon system: red ⚠ / amber ⚠ / green ✓ / amber ✗ / red ✗, with defaults of
4h / 1h / 1h / 2h spare or short. Thresholds are editable in Settings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-22 13:38:46 +00:00
parent fee388db4c
commit 75ac7591d2
7 changed files with 256 additions and 20 deletions

View file

@ -8,18 +8,24 @@ export async function configRoutes(app) {
// ── GET /api/config — all settings at once ──────────────────────────────
app.get('/api/config', async (req) => {
const [timeReqs, staffData, pickupData, generalTasks, lastReviewed, toleranceMins, workforceRota, workforceDepts, adjustments] =
await Promise.all([
getConfig('time_requirements', {}),
getConfig('staff_data', []),
getConfig('pickup_data', {}),
getConfig('general_tasks', []),
getConfig('last_reviewed', null),
getConfig('tolerance_minutes', 30),
getConfig('workforce_rota', null),
getConfig('workforce_departments', []),
getConfig('adjustments', []),
])
const [
timeReqs, staffData, pickupData, generalTasks, lastReviewed,
workforceRota, workforceDepts, adjustments,
warnOverRed, warnOverAmber, warnUnderAmber, warnUnderRed,
] = await Promise.all([
getConfig('time_requirements', {}),
getConfig('staff_data', []),
getConfig('pickup_data', {}),
getConfig('general_tasks', []),
getConfig('last_reviewed', null),
getConfig('workforce_rota', null),
getConfig('workforce_departments', []),
getConfig('adjustments', []),
getConfig('warn_over_red_hrs', 4),
getConfig('warn_over_amber_hrs', 1),
getConfig('warn_under_amber_hrs', 1),
getConfig('warn_under_red_hrs', 2),
])
const today = new Date()
const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`
@ -33,10 +39,13 @@ export async function configRoutes(app) {
pickup_data: pickupData || {},
general_tasks: generalTasks || [],
last_reviewed: lastReviewed || yestStr,
tolerance_minutes: toleranceMins != null ? toleranceMins : 30,
workforce_rota: workforceRota || null,
workforce_departments: workforceDepts || [],
adjustments: adjustments || [],
warn_over_red_hrs: warnOverRed != null ? warnOverRed : 4,
warn_over_amber_hrs: warnOverAmber != null ? warnOverAmber : 1,
warn_under_amber_hrs: warnUnderAmber != null ? warnUnderAmber : 1,
warn_under_red_hrs: warnUnderRed != null ? warnUnderRed : 2,
}
})
@ -161,6 +170,20 @@ export async function configRoutes(app) {
return { ok: true }
})
// ── PUT /api/config/warning-thresholds ──────────────────────────────────
app.put('/api/config/warning-thresholds', { preHandler: requireCap('settings') }, async (req, reply) => {
const { warn_over_red_hrs, warn_over_amber_hrs, warn_under_amber_hrs, warn_under_red_hrs } = req.body || {}
const parse = (v, def) => { const n = parseFloat(v); return isNaN(n) || n < 0 ? def : n }
await Promise.all([
setConfig('warn_over_red_hrs', parse(warn_over_red_hrs, 4)),
setConfig('warn_over_amber_hrs', parse(warn_over_amber_hrs, 1)),
setConfig('warn_under_amber_hrs', parse(warn_under_amber_hrs, 1)),
setConfig('warn_under_red_hrs', parse(warn_under_red_hrs, 2)),
])
return { ok: true }
})
// ── GET /api/categories — settings cap required ──────────────────────────
app.get('/api/categories', { preHandler: requireCap('settings') }, async (req, reply) => {