feat(ui): Nested task slots in worker dashboard

Backend:
- Add active_tasks array to GET /worker-registry/workers response
- Include task details: role, dispensary, running_seconds, progress

Frontend:
- Show nested task list under each worker with duration
- Display empty slots when worker has capacity
- Update pod visualization to show 3 task slot nodes
- Active slots pulse blue, empty slots gray
- Hover for task details (dispensary, duration, progress)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Kelly
2025-12-13 20:40:15 -07:00
parent b51ba17d32
commit 6439de5cd4
2 changed files with 194 additions and 73 deletions

View File

@@ -430,9 +430,59 @@ router.get('/workers', async (req: Request, res: Response) => {
FROM worker_registry
`);
// Get active tasks for all workers (for nested task view)
const { rows: activeTasks } = await pool.query(`
SELECT
t.worker_id,
t.id as task_id,
t.role,
t.status as task_status,
t.started_at,
t.progress,
t.progress_message,
EXTRACT(EPOCH FROM (NOW() - t.started_at))::int as running_seconds,
d.id as dispensary_id,
d.name as dispensary_name,
d.city as dispensary_city,
d.state as dispensary_state
FROM worker_tasks t
LEFT JOIN dispensaries d ON t.dispensary_id = d.id
WHERE t.status IN ('running', 'claimed')
ORDER BY t.worker_id, t.started_at
`);
// Group tasks by worker_id
const tasksByWorker: Record<string, any[]> = {};
for (const task of activeTasks) {
if (!tasksByWorker[task.worker_id]) {
tasksByWorker[task.worker_id] = [];
}
tasksByWorker[task.worker_id].push({
task_id: task.task_id,
role: task.role,
status: task.task_status,
started_at: task.started_at,
running_seconds: task.running_seconds,
progress: task.progress,
progress_message: task.progress_message,
dispensary: task.dispensary_name ? {
id: task.dispensary_id,
name: task.dispensary_name,
city: task.dispensary_city,
state: task.dispensary_state,
} : null,
});
}
// Attach tasks to each worker
const workersWithTasks = rows.map((worker: any) => ({
...worker,
active_tasks: tasksByWorker[worker.worker_id] || [],
}));
res.json({
success: true,
workers: rows,
workers: workersWithTasks,
summary: summary[0]
});
} catch (error: any) {