# 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
<Route path="/team-performance" element={<TeamPerformancePage />} />
<Route path="/live-monitor" element={<LiveMonitorPage />} />
<Route path="/call-recordings" element={<CallRecordingsPage />} />
<Route path="/missed-calls" element={<MissedCallsPage />} />
```

- [ ] **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 = () => <div>Team Performance — coming soon</div>;

// src/pages/live-monitor.tsx
export const LiveMonitorPage = () => <div>Live Call Monitor — coming soon</div>;

// src/pages/call-recordings.tsx
export const CallRecordingsPage = () => <div>Call Recordings — coming soon</div>;

// src/pages/missed-calls.tsx
export const MissedCallsPage = () => <div>Missed Calls — coming soon</div>;
```

- [ ] **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<string, ActiveCall>();

    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<any> {
        // Get all agent IDs from platform
        const agentData = await this.platform.query<any>(
            `{ 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
```
