saridsa2 04f559037c fix(appointment-form): filter past pills + confirm modal + view-only mode
- leadAppointments now filters out past appointments — past dates
  can't be rescheduled, so "14 Apr · Meena Patel" shouldn't appear in
  the pill row today. Uses scheduledAt >= now.
- Click Edit pill → reschedule-confirm modal:
    "Yes, reschedule" → form opens in edit mode (prefilled + editable)
    "No, just view"    → form opens read-only (prefilled + disabled)
- Prefill was broken — AppointmentForm's useState initializers only
  run at mount, so switching pills didn't re-seed state. Added
  key={editingApptId}-{apptMode} so the form fully remounts whenever
  the selection or mode changes.
- Thread readOnly prop through every form control (patient name,
  phone, age, gender, clinic, department, doctor, date, time slots,
  chief complaint). In view mode all inputs are disabled and the
  Update Appointment + Cancel Appointment buttons hide — only Close
  remains.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 13:39:54 +05:30

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

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:

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.tsxhandleDisposition() 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
Description
No description provided
Readme 4.5 MiB
Languages
TypeScript 96.1%
CSS 3.8%