# Helix Engage — Frontend

Call center CRM frontend for healthcare lead management. Built on the FortyTwo platform.

**Owner: Mouli**

## Architecture

```
┌─────────────────────┐     ┌──────────────────────┐     ┌─────────────────────┐
│   helix-engage      │     │ helix-engage-server   │     │  FortyTwo Platform  │
│   (this repo)       │────▶│ (sidecar)             │────▶│  (backend)          │
│   React frontend    │     │ NestJS REST API       │     │  GraphQL API        │
│   Port 5173 (dev)   │     │ Port 4100             │     │  Port 4000          │
└─────────────────────┘     └──────────────────────┘     └─────────────────────┘
         │                           │
         │    SIP/WebRTC             │    Ozonetel CloudAgent APIs
         ▼                           ▼
   ┌───────────┐              ┌──────────────┐
   │ Ozonetel  │              │ Ozonetel     │
   │ SIP (444) │              │ REST APIs    │
   └───────────┘              └──────────────┘
```

**Three repos:**
| Repo | Purpose | Owner |
|------|---------|-------|
| `helix-engage` (this) | React frontend | Mouli |
| `helix-engage-server` | NestJS sidecar — Ozonetel + Platform bridge | Karthik |
| `helix-engage-app` | FortyTwo SDK app — entity schemas (Call, Lead, etc.) | Shared |

## Getting Started

```bash
npm install
npm run dev       # http://localhost:5173
npm run build     # TypeScript check + production build
```

### Environment Variables (set at build time or in `.env`)

| Variable | Purpose | Dev Default | Production |
|----------|---------|-------------|------------|
| `VITE_API_URL` | Platform GraphQL | `http://localhost:4000` | `https://engage-api.srv1477139.hstgr.cloud` |
| `VITE_SIDECAR_URL` | Sidecar REST API | `http://localhost:4100` | `https://engage-api.srv1477139.hstgr.cloud` |
| `VITE_SIP_URI` | Ozonetel SIP URI | — | `sip:523590@blr-pub-rtc4.ozonetel.com` |
| `VITE_SIP_PASSWORD` | SIP password | — | `523590` |
| `VITE_SIP_WS_SERVER` | SIP WebSocket | — | `wss://blr-pub-rtc4.ozonetel.com:444` |

**Production build command:**
```bash
VITE_API_URL=https://engage-api.srv1477139.hstgr.cloud \
VITE_SIDECAR_URL=https://engage-api.srv1477139.hstgr.cloud \
VITE_SIP_URI=sip:523590@blr-pub-rtc4.ozonetel.com \
VITE_SIP_PASSWORD=523590 \
VITE_SIP_WS_SERVER=wss://blr-pub-rtc4.ozonetel.com:444 \
npm run build
```

## Tech Stack

- **React 19** + TypeScript + Vite
- **Tailwind CSS 4** with semantic color tokens (`text-primary`, `bg-brand-section` — never raw colors like `text-gray-900`)
- **React Aria Components** for accessibility (imports always prefixed `Aria*`)
- **Jotai** for SIP/call state
- **React Context** for auth, data, theme
- **FontAwesome Pro Duotone** icons
- **Untitled UI** component library (`src/components/base/`, `src/components/application/`)

## Project Structure

```
src/
├── pages/                  # Route-level pages
│   ├── call-desk.tsx       # Main CC agent workspace — THE CORE PAGE
│   ├── login.tsx           # Auth page (centered card on blue bg)
│   ├── call-history.tsx    # CDR log viewer
│   ├── my-performance.tsx  # Agent KPI dashboard
│   ├── team-dashboard.tsx  # Supervisor overview
│   ├── all-leads.tsx       # Lead master table
│   └── campaigns.tsx       # Campaign listing
│
├── components/
│   ├── call-desk/          # ⚡ Call center components — WHERE MOST WORK HAPPENS
│   │   ├── active-call-card.tsx    # In-call UI + post-call disposition flow
│   │   ├── worklist-panel.tsx      # Agent task queue with tabs + sub-tabs
│   │   ├── context-panel.tsx       # AI assistant + Lead 360 sidebar
│   │   ├── disposition-form.tsx    # Post-call outcome selector
│   │   ├── appointment-form.tsx    # Book appointment during/after call
│   │   ├── agent-status-toggle.tsx # Ready/Break/Training/Offline toggle
│   │   ├── transfer-dialog.tsx     # Call transfer
│   │   ├── enquiry-form.tsx        # General enquiry capture
│   │   ├── live-transcript.tsx     # Real-time transcription (Deepgram)
│   │   └── phone-action-cell.tsx   # Click-to-call in table rows
│   ├── base/               # Untitled UI primitives (Button, Input, Select, Badge)
│   ├── application/        # Complex UI (Table, Modal, Tabs, DatePicker, Nav)
│   ├── layout/             # Sidebar — role-based navigation
│   └── dashboard/          # KPI cards, charts, missed queue widget
│
├── providers/
│   ├── sip-provider.tsx    # SIP WebRTC — call lifecycle management
│   ├── auth-provider.tsx   # User session, roles (executive/admin/cc-agent)
│   ├── data-provider.tsx   # Bulk entity loader (leads, campaigns, calls)
│   └── theme-provider.tsx  # Light/dark mode
│
├── hooks/
│   ├── use-worklist.ts     # Polls sidecar /api/worklist every 30s
│   ├── use-call-assist.ts  # Live transcript via Socket.IO
│   └── use-sip-phone.ts    # Low-level SIP.js wrapper
│
├── lib/
│   ├── api-client.ts       # REST + GraphQL client (auth, queries, sidecar calls)
│   ├── queries.ts          # Platform GraphQL query strings
│   └── format.ts           # Phone/date formatting
│
├── state/
│   └── sip-state.ts        # Jotai atoms (callState, callerNumber, isMuted, etc.)
│
└── types/
    └── entities.ts         # Lead, Patient, Call, Appointment, etc.
```

## Troubleshooting Guide — Where to Look

### "The call desk isn't working"
**File:** `src/pages/call-desk.tsx`
This is the orchestrator. It uses `useSip()` for call state, `useWorklist()` for the task queue, and renders either `ActiveCallCard` (in-call) or `WorklistPanel` (idle). Start here, then drill into whichever child component is misbehaving.

### "Calls aren't connecting / SIP errors"
**File:** `src/providers/sip-provider.tsx` + `src/state/sip-state.ts`
Check `VITE_SIP_*` env vars. Ozonetel SIP WebSocket runs on **port 444** — VPNs block it. If WebSocket hangs at "connecting", turn off VPN. Also check browser console for SIP.js registration errors.

### "Worklist not loading / empty"
**File:** `src/hooks/use-worklist.ts`
This polls `GET /api/worklist` on the sidecar every 30s. Open browser Network tab → filter for `/api/worklist`. Common causes: sidecar is down, auth token expired, or agent name doesn't match any assigned leads.

### "Missed calls not appearing / sub-tabs empty"
**File:** `src/components/call-desk/worklist-panel.tsx`
Missed calls come from the sidecar worklist response. The sub-tabs filter by `callbackstatus` field. If all sub-tabs are empty, the sidecar ingestion may not be running (check sidecar logs for `MissedQueueService`).

### "Disposition / appointment not saving"
**File:** `src/components/call-desk/active-call-card.tsx` → `handleDisposition()`
Posts to sidecar `POST /api/ozonetel/dispose`. Errors are caught silently (non-blocking). Check browser Network tab for the dispose request/response, then check sidecar logs.

### "Login broken / Failed to fetch"
**File:** `src/pages/login.tsx` + `src/lib/api-client.ts`
Login calls `apiClient.login()` → sidecar `/auth/login` → platform GraphQL. Most common cause: wrong `VITE_API_URL` (built with localhost instead of production URL). **Always set env vars at build time.**

### "UI component looks wrong"
**Files:** `src/components/base/` (primitives), `src/components/application/` (complex)
These come from the Untitled UI library. Design tokens are in `src/styles/theme.css`. Brand colors were rebuilt from logo blue `rgb(32, 96, 160)`.

### "Navigation / role-based access"
**File:** `src/components/layout/sidebar.tsx`
Navigation groups are defined per role (admin, cc-agent, executive). Routes are registered in `src/main.tsx`.

## Data Flow

```
User action
    │
    ▼
Component (e.g. ActiveCallCard)
    │
    ├──▶ Sidecar REST API (via apiClient.post/get)
    │       e.g. /api/ozonetel/dispose, /api/worklist
    │
    ├──▶ Platform GraphQL (via apiClient.graphql)
    │       e.g. leads, appointments, patients queries
    │
    └──▶ SIP.js (via useSip() hook)
            Call control: answer, hangup, mute, hold
```

**Key pattern:** The frontend talks to TWO backends:
1. **Sidecar** (REST) — for Ozonetel telephony operations and worklist
2. **Platform** (GraphQL) — for entity CRUD (leads, appointments, patients)

## Conventions

- **File naming**: kebab-case (`worklist-panel.tsx`)
- **Colors**: Semantic tokens only (`text-primary`, `bg-brand-section`)
- **Icons**: `@fortawesome/pro-duotone-svg-icons` + `faIcon()` wrapper in `src/lib/icon-wrapper.ts`
- **React Aria**: Always prefix imports (`Button as AriaButton`)
- **Transitions**: `transition duration-100 ease-linear`

## Git Workflow

- `dev` — active development
- `master` — stable baseline
- Always build with production env vars before deploying
