mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
docs: supervisor module spec + implementation plan
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
531
docs/superpowers/plans/2026-03-24-supervisor-module.md
Normal file
531
docs/superpowers/plans/2026-03-24-supervisor-module.md
Normal file
@@ -0,0 +1,531 @@
|
||||
# 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
|
||||
```
|
||||
191
docs/superpowers/specs/2026-03-24-supervisor-module.md
Normal file
191
docs/superpowers/specs/2026-03-24-supervisor-module.md
Normal file
@@ -0,0 +1,191 @@
|
||||
# Supervisor Module — Team Performance, Live Call Monitor, Master Data
|
||||
|
||||
**Date**: 2026-03-24
|
||||
**Jira**: PP-5 (Team Performance), PP-6 (Live Call Monitor)
|
||||
**Status**: Approved design
|
||||
|
||||
---
|
||||
|
||||
## Principle
|
||||
|
||||
No hardcoded/mock data. All data from Ozonetel APIs or platform GraphQL queries.
|
||||
|
||||
---
|
||||
|
||||
## 1. Admin Sidebar Nav Restructure
|
||||
|
||||
```
|
||||
SUPERVISOR
|
||||
Dashboard → / (existing team-dashboard.tsx — summary)
|
||||
Team Performance → /team-performance (new — full PP-5)
|
||||
Live Call Monitor → /live-monitor (new — PP-6)
|
||||
|
||||
DATA & REPORTS
|
||||
Lead Master → /leads (existing all-leads.tsx)
|
||||
Patient Master → /patients (existing patients.tsx)
|
||||
Appointment Master → /appointments (existing appointments.tsx)
|
||||
Call Log Master → /call-history (existing call-history.tsx)
|
||||
Call Recordings → /call-recordings (new — filtered calls with recordings)
|
||||
Missed Calls → /missed-calls (new — standalone missed call table)
|
||||
```
|
||||
|
||||
**Files**: `sidebar.tsx` (admin nav config), `main.tsx` (routes)
|
||||
|
||||
---
|
||||
|
||||
## 2. Team Performance Dashboard (PP-5)
|
||||
|
||||
**Route**: `/team-performance`
|
||||
**Page**: `src/pages/team-performance.tsx`
|
||||
|
||||
### Section 1: Key Metrics Bar
|
||||
- Active Agents / On Call Now → sidecar (from active calls tracking)
|
||||
- Total Calls → platform `calls` count by date range
|
||||
- Appointments → platform `appointments` count
|
||||
- Missed Calls → platform `calls` where `callStatus: MISSED`
|
||||
- Conversion Rate → appointments / total calls
|
||||
- Time filter: Today | Week | Month | Year | Custom
|
||||
|
||||
### Section 2: Call Breakdown Trends
|
||||
- Left: Inbound vs Outbound line chart (ECharts) by day
|
||||
- Right: Leads vs Missed vs Follow-ups by day
|
||||
- Data: platform `calls` grouped by date + direction
|
||||
|
||||
### Section 3: Agent Performance Table
|
||||
| Column | Source |
|
||||
|--------|--------|
|
||||
| Agent | Agent entity `name` |
|
||||
| Calls | Platform `calls` filtered by `agentName` |
|
||||
| Inbound | Platform `calls` where `direction: INBOUND` |
|
||||
| Missed | Platform `calls` where `callStatus: MISSED` |
|
||||
| Follow-ups | Platform `followUps` filtered by `assignedAgent` |
|
||||
| Leads | Platform `leads` filtered by `assignedAgent` |
|
||||
| Conv% | Derived: appointments / calls |
|
||||
| NPS | Agent entity `npsscore` |
|
||||
| Idle | Ozonetel `getAgentSummary` API |
|
||||
|
||||
Sortable columns. Own time filter (Today/Week/Month/Year/Custom).
|
||||
|
||||
### Section 4: Time Breakdown
|
||||
- Team average: Active / Wrap / Idle / Break totals
|
||||
- Per-agent horizontal stacked bars
|
||||
- Data: Ozonetel `getAgentSummary` per agent
|
||||
- Agents with idle > `maxidleminutes` threshold highlighted red
|
||||
|
||||
### Section 5: NPS + Conversion Metrics
|
||||
- NPS donut chart (average of all agents' `npsscore`)
|
||||
- Per-agent NPS horizontal bars
|
||||
- Call→Appointment % card (big number)
|
||||
- Lead→Contact % card (big number)
|
||||
- Per-agent conversion breakdown below cards
|
||||
|
||||
### Section 6: Performance Alerts
|
||||
- Compare actual metrics vs Agent entity thresholds:
|
||||
- `maxidleminutes` → "Excessive Idle Time"
|
||||
- `minnpsthreshold` → "Low NPS"
|
||||
- `minconversionpercent` → "Low Lead-to-Contact"
|
||||
- Red-highlighted alert cards with agent name, alert type, value
|
||||
|
||||
### Sidecar Endpoint
|
||||
`GET /api/supervisor/team-performance?date=YYYY-MM-DD`
|
||||
- Aggregates Ozonetel `getAgentSummary` across all agents
|
||||
- Returns per-agent time breakdown (active/wrap/idle/break in minutes)
|
||||
- Uses Agent entity to get list of all agent IDs
|
||||
|
||||
---
|
||||
|
||||
## 3. Live Call Monitor (PP-6)
|
||||
|
||||
**Route**: `/live-monitor`
|
||||
**Page**: `src/pages/live-monitor.tsx`
|
||||
|
||||
### KPI Cards
|
||||
- Active Calls count
|
||||
- On Hold count
|
||||
- Avg Duration
|
||||
|
||||
### Active Calls Table
|
||||
| Column | Source |
|
||||
|--------|--------|
|
||||
| Agent | Ozonetel event `agent_id` → mapped to Agent entity name |
|
||||
| Caller | Event `caller_id` → matched against platform leads/patients |
|
||||
| Type | Event `call_type` (InBound/Manual) |
|
||||
| Department | From matched lead's `interestedService` or "—" |
|
||||
| Duration | Live counter from `event_time` |
|
||||
| Status | active / on-hold |
|
||||
| Actions | Listen / Whisper / Barge buttons (disabled until API confirmed) |
|
||||
|
||||
### Data Flow
|
||||
1. Sidecar subscribes to Ozonetel real-time events on startup
|
||||
- `POST https://subscription.ozonetel.com/events/subscribe`
|
||||
- Body: `{ callEventsURL: "<sidecar-webhook-url>", agentEventsURL: "<sidecar-webhook-url>" }`
|
||||
2. Sidecar receives events at `POST /webhooks/ozonetel/call-event`
|
||||
3. In-memory map: `ucid → { agentId, callerNumber, callType, startTime, status }`
|
||||
- `Calling` / `Answered` → add/update entry
|
||||
- `Disconnect` → remove entry
|
||||
4. `GET /api/supervisor/active-calls` → returns current map
|
||||
5. Frontend polls every 5 seconds
|
||||
|
||||
### Sidecar Changes
|
||||
- New module: `src/supervisor/`
|
||||
- `supervisor.controller.ts` — team-performance + active-calls endpoints
|
||||
- `supervisor.service.ts` — Ozonetel event subscription, active call tracking
|
||||
- `supervisor.module.ts`
|
||||
- New webhook: `POST /webhooks/ozonetel/call-event`
|
||||
- Ozonetel event subscription on `onModuleInit`
|
||||
|
||||
---
|
||||
|
||||
## 4. Master Data Pages
|
||||
|
||||
### Call Recordings (`/call-recordings`)
|
||||
**Page**: `src/pages/call-recordings.tsx`
|
||||
- Query: platform `calls` where `recording` is not null
|
||||
- Table: Agent, Caller, Type, Date, Duration, Recording Player
|
||||
- Search by agent/phone + date filter
|
||||
|
||||
### Missed Calls (`/missed-calls`)
|
||||
**Page**: `src/pages/missed-calls.tsx`
|
||||
- Query: platform `calls` where `callStatus: MISSED`
|
||||
- Table: Caller, Date/Time, Branch (`callsourcenumber`), Agent, Callback Status, SLA
|
||||
- Tabs: All | Pending | Attempted | Completed (filter by `callbackstatus`)
|
||||
- Not filtered by agent — supervisor sees all
|
||||
|
||||
---
|
||||
|
||||
## 5. Agent Entity Fields (Already Configured)
|
||||
|
||||
| GraphQL Field | Type | Purpose |
|
||||
|---|---|---|
|
||||
| `ozonetelagentid` | Text | Ozonetel agent ID |
|
||||
| `sipextension` | Text | SIP extension |
|
||||
| `sippassword` | Text | SIP password |
|
||||
| `campaignname` | Text | Ozonetel campaign |
|
||||
| `npsscore` | Number | Agent NPS score |
|
||||
| `maxidleminutes` | Number | Idle time alert threshold |
|
||||
| `minnpsthreshold` | Number | NPS alert threshold |
|
||||
| `minconversionpercent` | Number | Conversion alert threshold |
|
||||
|
||||
All custom fields use **all-lowercase** GraphQL names.
|
||||
|
||||
---
|
||||
|
||||
## 6. File Map
|
||||
|
||||
### New Files
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `helix-engage/src/pages/team-performance.tsx` | PP-5 dashboard |
|
||||
| `helix-engage/src/pages/live-monitor.tsx` | PP-6 active call monitor |
|
||||
| `helix-engage/src/pages/call-recordings.tsx` | Call recordings master |
|
||||
| `helix-engage/src/pages/missed-calls.tsx` | Missed calls master |
|
||||
| `helix-engage-server/src/supervisor/supervisor.controller.ts` | Supervisor endpoints |
|
||||
| `helix-engage-server/src/supervisor/supervisor.service.ts` | Event subscription + active calls |
|
||||
| `helix-engage-server/src/supervisor/supervisor.module.ts` | Module registration |
|
||||
|
||||
### Modified Files
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `helix-engage/src/components/layout/sidebar.tsx` | Admin nav restructure |
|
||||
| `helix-engage/src/main.tsx` | New routes |
|
||||
| `helix-engage-server/src/app.module.ts` | Import SupervisorModule |
|
||||
Reference in New Issue
Block a user