Deputy Header Integration
Real-time on-shift staff count in the admin header, powered by Deputy API.
Overview
The header displays a live count of staff currently on shift, queried from Deputy's timesheet and roster data. This provides instant visibility into current staffing levels.
┌────────────────────────────────────────────────────────────┐
│ RABS: [Ticker Message] 👥? 👤3 💬0 ⛶ 📅 🔔 ⊞ 👤 ⚙️ │
│ ↑ │
│ On-shift count │
└────────────────────────────────────────────────────────────┘
Data Sources
Primary: Timesheets
Queries Deputy timesheets where:
StartTime >= today midnightStartTime <= nowEndTime = 0(indicates shift still active)
Fallback: Rosters
If no active timesheets found (e.g., staff don't clock in), falls back to:
- Rosters where
StartTime <= now AND EndTime >= now
API
Endpoint
GET /api/v1/staff/deputy/on-shift
Response
{
"success": true,
"data": {
"count": 3,
"timesheets": 3, // or "rosters": 5
"method": "timesheet", // or "roster"
"asOf": "2026-01-07T14:30:00.000Z"
}
}
Backend Implementation
Location
backend/services/deputy.js
Functions
// Primary function - tries timesheets first
async function getOnShiftCount() {
// Query timesheets with EndTime = 0
// Falls back to rosters if no results
}
// Fallback function
async function getOnShiftCountFromRosters() {
// Query rosters overlapping current time
}
Route
backend/routes_v1p/staff_deputy.js
router.get('/on-shift', async (req, res) => {
const result = await deputy.getOnShiftCount();
res.json({ success: true, data: result });
});
Frontend Implementation
Location
admin/src/js/shared/ticker.js
Functions
// Fetch current count
async function fetchStaffOnShift() {
const res = await fetch('/api/v1/staff/deputy/on-shift', {...});
return data.success ? data.data : null;
}
// Update DOM elements
function updateStaffCounter(data) {
const els = document.querySelectorAll('[data-staff-on-shift-count]');
els.forEach(el => {
el.textContent = String(data?.count ?? '—');
el.title = `As of ${new Date(data.asOf).toLocaleTimeString()}`;
});
}
// Initialize with 2-minute refresh
async function initStaffCounter() {
const data = await fetchStaffOnShift();
updateStaffCounter(data);
setInterval(async () => {
updateStaffCounter(await fetchStaffOnShift());
}, 120000); // 2 minutes
}
HTML Element
admin/src/html/partials/app-header.html
<a href="page_staff.html" class="menu-link" data-bs-toggle="tooltip"
data-bs-placement="bottom" data-bs-title="Staff on shift">
<div class="menu-icon">
<i class="nav-icon bi-person-badge"></i>
<span class="ms-1" data-staff-on-shift-count>—</span>
</div>
</a>
Refresh Behavior
| Event | Action |
|---|---|
| Page load | Immediate fetch |
| Every 2 minutes | Background refresh |
| Tooltip | Shows timestamp of last update |
Error Handling
API Errors
- Console warning logged
- Counter shows
—(em dash) - Continues polling for recovery
No Active Shifts
- Timesheet query returns 0 → automatically tries rosters
- If both return 0 → displays
0(legitimate result)
Deputy Not Configured
- Endpoint returns 503
- Counter shows
—
Tooltip Information
The counter tooltip indicates data source:
- Timesheet method:
As of 2:30:45 PM - Roster method:
Based on roster (timesheets unavailable)
Performance
- API Call: ~200-500ms (Deputy API latency)
- Refresh: Every 2 minutes (conservative to avoid rate limits)
- Caching: None currently (real-time data important)
Future Enhancements
- Click to expand - Show list of who's on shift
- Shift change notifications - Alert when someone clocks in/out
- Location breakdown - Count per site/department
- Historical graph - Staffing levels over time
- Cache layer - Reduce Deputy API calls with 30-60s cache