# Supervisor Module Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Build the supervisor module with team performance dashboard (PP-5), live call monitor (PP-6), master data pages, and admin sidebar restructure. **Architecture:** Frontend pages query platform GraphQL directly for entity data (calls, appointments, leads, agents). Sidecar provides Ozonetel-specific data (agent time breakdown, active calls via event subscription). No hardcoded/mock data anywhere. **Tech Stack:** React + Tailwind + ECharts (frontend), NestJS sidecar (Ozonetel integration), Fortytwo platform GraphQL **Spec:** `docs/superpowers/specs/2026-03-24-supervisor-module.md` --- ## File Map ### Frontend (`helix-engage/src/`) | File | Action | Responsibility | |------|--------|----------------| | `pages/team-performance.tsx` | Create | PP-5 full dashboard | | `pages/live-monitor.tsx` | Create | PP-6 active call table | | `pages/call-recordings.tsx` | Create | Calls with recordings master | | `pages/missed-calls.tsx` | Create | Missed calls master (supervisor view) | | `components/layout/sidebar.tsx` | Modify | Admin nav restructure | | `main.tsx` | Modify | Add new routes | ### Sidecar (`helix-engage-server/src/`) | File | Action | Responsibility | |------|--------|----------------| | `supervisor/supervisor.service.ts` | Create | Team perf aggregation + active call tracking | | `supervisor/supervisor.controller.ts` | Create | REST endpoints | | `supervisor/supervisor.module.ts` | Create | Module registration | | `app.module.ts` | Modify | Import SupervisorModule | --- ## Task 1: Admin Sidebar Nav + Routes **Files:** - Modify: `helix-engage/src/components/layout/sidebar.tsx` - Modify: `helix-engage/src/main.tsx` - [ ] **Step 1: Add new icon imports to sidebar** In `sidebar.tsx`, add to the FontAwesome imports: ```typescript import { // existing imports... faRadio, faFileAudio, faPhoneMissed, faChartLine, } from '@fortawesome/pro-duotone-svg-icons'; ``` Add icon wrappers: ```typescript const IconRadio = faIcon(faRadio); const IconFileAudio = faIcon(faFileAudio); const IconPhoneMissed = faIcon(faPhoneMissed); const IconChartLine = faIcon(faChartLine); ``` - [ ] **Step 2: Restructure admin nav** Replace the admin nav section (currently has Overview + Management + Admin groups) with: ```typescript if (role === 'admin') { return [ { label: 'Supervisor', items: [ { label: 'Dashboard', href: '/', icon: IconGrid2 }, { label: 'Team Performance', href: '/team-performance', icon: IconChartLine }, { label: 'Live Call Monitor', href: '/live-monitor', icon: IconRadio }, ]}, { label: 'Data & Reports', items: [ { label: 'Lead Master', href: '/leads', icon: IconUsers }, { label: 'Patient Master', href: '/patients', icon: IconHospitalUser }, { label: 'Appointment Master', href: '/appointments', icon: IconCalendarCheck }, { label: 'Call Log Master', href: '/call-history', icon: IconClockRewind }, { label: 'Call Recordings', href: '/call-recordings', icon: IconFileAudio }, { label: 'Missed Calls', href: '/missed-calls', icon: IconPhoneMissed }, ]}, { label: 'Admin', items: [ { label: 'Settings', href: '/settings', icon: IconGear }, ]}, ]; } ``` - [ ] **Step 3: Add routes in main.tsx** Import new page components (they'll be created in later tasks — use placeholder components for now): ```typescript import { TeamPerformancePage } from "@/pages/team-performance"; import { LiveMonitorPage } from "@/pages/live-monitor"; import { CallRecordingsPage } from "@/pages/call-recordings"; import { MissedCallsPage } from "@/pages/missed-calls"; ``` Add routes: ```typescript } /> } /> } /> } /> ``` - [ ] **Step 4: Create placeholder pages** Create minimal placeholder files for each new page so the build doesn't fail: ```typescript // src/pages/team-performance.tsx export const TeamPerformancePage = () =>
Team Performance — coming soon
; // src/pages/live-monitor.tsx export const LiveMonitorPage = () =>
Live Call Monitor — coming soon
; // src/pages/call-recordings.tsx export const CallRecordingsPage = () =>
Call Recordings — coming soon
; // src/pages/missed-calls.tsx export const MissedCallsPage = () =>
Missed Calls — coming soon
; ``` - [ ] **Step 5: Verify build** ```bash cd helix-engage && npm run build ``` - [ ] **Step 6: Commit** ```bash git add src/components/layout/sidebar.tsx src/main.tsx src/pages/team-performance.tsx src/pages/live-monitor.tsx src/pages/call-recordings.tsx src/pages/missed-calls.tsx git commit -m "feat: admin sidebar restructure + placeholder pages for supervisor module" ``` --- ## Task 2: Call Recordings Page **Files:** - Modify: `helix-engage/src/pages/call-recordings.tsx` - [ ] **Step 1: Implement call recordings page** Query platform for calls with recordings. Reuse patterns from `call-history.tsx`. ```typescript // Query: calls where recording primaryLinkUrl is not empty const QUERY = `{ calls(first: 100, filter: { recording: { primaryLinkUrl: { neq: "" } } }, orderBy: [{ startedAt: DescNullsLast }]) { edges { node { id direction callStatus callerNumber { primaryPhoneNumber } agentName startedAt durationSec disposition recording { primaryLinkUrl primaryLinkLabel } } } } }`; ``` Table columns: Agent, Caller (PhoneActionCell), Type (In/Out badge), Date, Duration, Disposition, Recording (play button). Search by agent name or phone number. Date filter optional. - [ ] **Step 2: Verify build** ```bash cd helix-engage && npm run build ``` - [ ] **Step 3: Commit** ```bash git add src/pages/call-recordings.tsx git commit -m "feat: call recordings master page" ``` --- ## Task 3: Missed Calls Page (Supervisor View) **Files:** - Modify: `helix-engage/src/pages/missed-calls.tsx` - [ ] **Step 1: Implement missed calls page** Query platform for all missed calls — no agent filter (supervisor sees all). ```typescript const QUERY = `{ calls(first: 100, filter: { callStatus: { eq: MISSED } }, orderBy: [{ startedAt: DescNullsLast }]) { edges { node { id callerNumber { primaryPhoneNumber } agentName startedAt callsourcenumber callbackstatus missedcallcount callbackattemptedat } } } }`; ``` Table columns: Caller (PhoneActionCell), Date/Time, Branch (`callsourcenumber`), Agent, Callback Status (badge), SLA (computed from `startedAt`). Tabs: All | Pending (`PENDING_CALLBACK`) | Attempted (`CALLBACK_ATTEMPTED`) | Completed (`CALLBACK_COMPLETED` + `WRONG_NUMBER`). Search by phone or agent. - [ ] **Step 2: Verify build** ```bash cd helix-engage && npm run build ``` - [ ] **Step 3: Commit** ```bash git add src/pages/missed-calls.tsx git commit -m "feat: missed calls master page for supervisors" ``` --- ## Task 4: Sidecar — Supervisor Module **Files:** - Create: `helix-engage-server/src/supervisor/supervisor.service.ts` - Create: `helix-engage-server/src/supervisor/supervisor.controller.ts` - Create: `helix-engage-server/src/supervisor/supervisor.module.ts` - Modify: `helix-engage-server/src/app.module.ts` - [ ] **Step 1: Create supervisor service** ```typescript // supervisor.service.ts // Two responsibilities: // 1. Aggregate Ozonetel agent summary across all agents // 2. Track active calls from Ozonetel real-time events import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { PlatformGraphqlService } from '../platform/platform-graphql.service'; import { OzonetelAgentService } from '../ozonetel/ozonetel-agent.service'; type ActiveCall = { ucid: string; agentId: string; callerNumber: string; callType: string; startTime: string; status: 'active' | 'on-hold'; }; @Injectable() export class SupervisorService implements OnModuleInit { private readonly logger = new Logger(SupervisorService.name); private readonly activeCalls = new Map(); constructor( private platform: PlatformGraphqlService, private ozonetel: OzonetelAgentService, private config: ConfigService, ) {} async onModuleInit() { // Subscribe to Ozonetel events (fire and forget) // Will be implemented when webhook URL is configured this.logger.log('Supervisor service initialized'); } // Called by webhook when Ozonetel pushes call events handleCallEvent(event: any) { const { action, ucid, agent_id, caller_id, call_type, event_time } = event; if (action === 'Answered' || action === 'Calling') { this.activeCalls.set(ucid, { ucid, agentId: agent_id, callerNumber: caller_id, callType: call_type, startTime: event_time, status: 'active', }); } else if (action === 'Disconnect') { this.activeCalls.delete(ucid); } } // Called by webhook when Ozonetel pushes agent events handleAgentEvent(event: any) { this.logger.log(`Agent event: ${event.agentId} → ${event.action}`); } getActiveCalls(): ActiveCall[] { return Array.from(this.activeCalls.values()); } // Aggregate time breakdown across all agents async getTeamPerformance(date: string): Promise { // Get all agent IDs from platform const agentData = await this.platform.query( `{ agents(first: 20) { edges { node { id name ozonetelagentid npsscore maxidleminutes minnpsthreshold minconversionpercent } } } }`, ); const agents = agentData?.agents?.edges?.map((e: any) => e.node) ?? []; // Fetch Ozonetel summary per agent const summaries = await Promise.all( agents.map(async (agent: any) => { try { const summary = await this.ozonetel.getAgentSummary(agent.ozonetelagentid, date); return { ...agent, timeBreakdown: summary }; } catch { return { ...agent, timeBreakdown: null }; } }), ); return { date, agents: summaries }; } } ``` - [ ] **Step 2: Create supervisor controller** ```typescript // supervisor.controller.ts import { Controller, Get, Post, Body, Query, Logger } from '@nestjs/common'; import { SupervisorService } from './supervisor.service'; @Controller('api/supervisor') export class SupervisorController { private readonly logger = new Logger(SupervisorController.name); constructor(private readonly supervisor: SupervisorService) {} @Get('active-calls') getActiveCalls() { return this.supervisor.getActiveCalls(); } @Get('team-performance') async getTeamPerformance(@Query('date') date?: string) { const targetDate = date ?? new Date().toISOString().split('T')[0]; return this.supervisor.getTeamPerformance(targetDate); } @Post('call-event') handleCallEvent(@Body() body: any) { // Ozonetel pushes events here const event = body.data ?? body; this.logger.log(`Call event: ${event.action} ucid=${event.ucid} agent=${event.agent_id}`); this.supervisor.handleCallEvent(event); return { received: true }; } @Post('agent-event') handleAgentEvent(@Body() body: any) { const event = body.data ?? body; this.logger.log(`Agent event: ${event.action} agent=${event.agentId}`); this.supervisor.handleAgentEvent(event); return { received: true }; } } ``` - [ ] **Step 3: Create supervisor module and register** ```typescript // supervisor.module.ts import { Module } from '@nestjs/common'; import { PlatformModule } from '../platform/platform.module'; import { OzonetelAgentModule } from '../ozonetel/ozonetel-agent.module'; import { SupervisorController } from './supervisor.controller'; import { SupervisorService } from './supervisor.service'; @Module({ imports: [PlatformModule, OzonetelAgentModule], controllers: [SupervisorController], providers: [SupervisorService], }) export class SupervisorModule {} ``` Add to `app.module.ts`: ```typescript import { SupervisorModule } from './supervisor/supervisor.module'; // Add to imports array ``` - [ ] **Step 4: Verify sidecar build** ```bash cd helix-engage-server && npm run build ``` - [ ] **Step 5: Commit** ```bash git add src/supervisor/ src/app.module.ts git commit -m "feat: supervisor module with team performance + active calls endpoints" ``` --- ## Task 5: Team Performance Dashboard (PP-5) **Files:** - Modify: `helix-engage/src/pages/team-performance.tsx` This is the largest task. The page queries platform directly for calls/appointments/leads and the sidecar for time breakdown. - [ ] **Step 1: Build the full page** The page has 6 sections. Use `apiClient.graphql()` for platform data and `apiClient.get()` for sidecar data. **Queries needed:** - Calls by date range: `calls(first: 500, filter: { startedAt: { gte: "...", lte: "..." } })` - Appointments by date range: `appointments(first: 200, filter: { scheduledAt: { gte: "...", lte: "..." } })` - Leads: `leads(first: 200)` - Follow-ups: `followUps(first: 200)` - Agents with thresholds: `agents(first: 20) { ... npsscore maxidleminutes minnpsthreshold minconversionpercent }` - Sidecar: `GET /api/supervisor/team-performance?date=YYYY-MM-DD` **Date range logic:** - Today: today start → now - Week: Monday of current week → now - Month: 1st of current month → now - Year: Jan 1 → now - Custom: user-selected range **Sections to implement:** 1. Key Metrics bar (6 cards in a row) 2. Call Breakdown Trends (2 ECharts line charts side by side) 3. Agent Performance table (sortable) 4. Time Breakdown (team average + per-agent stacked bars) 5. NPS + Conversion Metrics (donut + cards) 6. Performance Alerts (threshold comparison) Check if ECharts is already installed: ```bash grep echarts helix-engage/package.json ``` If not, install: `npm install echarts echarts-for-react` Follow the existing My Performance page (`my-performance.tsx`) for ECharts patterns. - [ ] **Step 2: Verify build** ```bash cd helix-engage && npm run build ``` - [ ] **Step 3: Test locally** ```bash cd helix-engage && npm run dev ``` Navigate to `/team-performance` as admin user. Verify all 6 sections render with real data. - [ ] **Step 4: Commit** ```bash git add src/pages/team-performance.tsx package.json package-lock.json git commit -m "feat: team performance dashboard (PP-5) with 6 data sections" ``` --- ## Task 6: Live Call Monitor (PP-6) **Files:** - Modify: `helix-engage/src/pages/live-monitor.tsx` - [ ] **Step 1: Build the live monitor page** Page polls `GET /api/supervisor/active-calls` every 5 seconds. **Structure:** 1. TopBar: "Live Call Monitor" with subtitle "Listen, whisper, or barge into active calls" 2. Three KPI cards: Active Calls, On Hold, Avg Duration 3. Active Calls table: Agent, Caller, Type, Department, Duration (live counter), Status, Actions 4. Actions: Listen / Whisper / Barge buttons — all disabled with tooltip "Coming soon — pending Ozonetel API" 5. Empty state: headphones icon + "No active calls" Duration should be a live counter — calculated client-side from `startTime` in the active call data. Use `setInterval` to update every second. Caller name: attempt to match `callerNumber` against leads from `useData()`. If matched, show lead name + phone. If not, show phone only. - [ ] **Step 2: Verify build** ```bash cd helix-engage && npm run build ``` - [ ] **Step 3: Test locally** Navigate to `/live-monitor`. Verify empty state renders. If Ozonetel events are flowing, verify active calls appear. - [ ] **Step 4: Commit** ```bash git add src/pages/live-monitor.tsx git commit -m "feat: live call monitor page (PP-6) with polling + KPI cards" ``` --- ## Task 7: Local Testing + Final Verification - [ ] **Step 1: Run both locally** Terminal 1: `cd helix-engage-server && npm run start:dev` Terminal 2: `cd helix-engage && npm run dev` - [ ] **Step 2: Test admin login** Login as admin (sanjay.marketing@globalhospital.com). Verify: - Sidebar shows new nav structure (Supervisor + Data & Reports sections) - Dashboard loads - Team Performance shows data from platform - Live Monitor shows empty state or active calls - All master data pages load (Lead, Patient, Appointment, Call Log, Call Recordings, Missed Calls) - [ ] **Step 3: Commit any fixes** - [ ] **Step 4: Push to Azure** ```bash cd helix-engage && git push origin dev cd helix-engage-server && git push origin dev ```