Files
helix-engage/src/pages/call-desk.tsx
saridsa2 d9d98bce9c feat: dashboard restructure, integrations, settings, UI fixes
Dashboard:
- Split into components (kpi-cards, agent-table, missed-queue)
- Add collapsible AI panel on right (same pattern as Call Desk)
- Add tabs: Agent Performance | Missed Queue | Campaigns
- Date range filter in header

Integrations page:
- Ozonetel (connected), WhatsApp, Facebook, Google, Instagram, Website, Email
- Status badges, config details, webhook URL with copy button

Settings page:
- Employee table from workspaceMembers GraphQL query
- Name, email, roles, status, reset password action

Fixes:
- Fix CALLS_QUERY: callerNumber needs { primaryPhoneNumber }, recordingUrl → recording { primaryLinkUrl }
- Remove duplicate AI Assistant header
- Remove Follow-ups from CC agent sidebar (already in worklist tabs)
- Remove global search from TopBar (decorative, unused)
- Slim down TopBar height
- Fix search/table gap in worklist
- Add brand border to active nav item

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 15:58:31 +05:30

105 lines
5.2 KiB
TypeScript

import { useState } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSidebarFlip, faSidebar } from '@fortawesome/pro-duotone-svg-icons';
import { useAuth } from '@/providers/auth-provider';
import { useData } from '@/providers/data-provider';
import { useWorklist } from '@/hooks/use-worklist';
import { useSip } from '@/providers/sip-provider';
import { WorklistPanel } from '@/components/call-desk/worklist-panel';
import type { WorklistLead } from '@/components/call-desk/worklist-panel';
import { ContextPanel } from '@/components/call-desk/context-panel';
import { ActiveCallCard } from '@/components/call-desk/active-call-card';
import { CallPrepCard } from '@/components/call-desk/call-prep-card';
import { BadgeWithDot, Badge } from '@/components/base/badges/badges';
import { cx } from '@/utils/cx';
export const CallDeskPage = () => {
const { user } = useAuth();
const { leadActivities } = useData();
const { connectionStatus, isRegistered, callState, callerNumber } = useSip();
const { missedCalls, followUps, marketingLeads, totalPending, loading } = useWorklist();
const [selectedLead, setSelectedLead] = useState<WorklistLead | null>(null);
const [contextOpen, setContextOpen] = useState(true);
const isInCall = callState === 'ringing-in' || callState === 'ringing-out' || callState === 'active';
const callerLead = callerNumber
? marketingLeads.find((l) => l.contactPhone?.[0]?.number?.endsWith(callerNumber) || callerNumber.endsWith(l.contactPhone?.[0]?.number ?? '---'))
: null;
const activeLead = isInCall ? (callerLead ?? selectedLead) : selectedLead;
const activeLeadFull = activeLead as any;
return (
<div className="flex flex-1 flex-col overflow-hidden">
{/* Compact header: title + name on left, status + toggle on right */}
<div className="flex shrink-0 items-center justify-between border-b border-secondary px-6 py-3">
<div className="flex items-center gap-3">
<h1 className="text-lg font-bold text-primary">Call Desk</h1>
<span className="text-sm text-tertiary">{user.name}</span>
</div>
<div className="flex items-center gap-2">
<BadgeWithDot
color={isRegistered ? 'success' : connectionStatus === 'connecting' ? 'warning' : 'gray'}
size="sm"
type="pill-color"
>
{isRegistered ? 'Ready' : connectionStatus}
</BadgeWithDot>
{totalPending > 0 && (
<Badge size="sm" color="brand" type="pill-color">{totalPending} pending</Badge>
)}
<button
onClick={() => setContextOpen(!contextOpen)}
className="flex size-8 items-center justify-center rounded-lg text-fg-quaternary hover:text-fg-secondary hover:bg-primary_hover transition duration-100 ease-linear"
title={contextOpen ? 'Hide AI panel' : 'Show AI panel'}
>
<FontAwesomeIcon icon={contextOpen ? faSidebarFlip : faSidebar} className="size-4" />
</button>
</div>
</div>
{/* Main content */}
<div className="flex flex-1 overflow-hidden">
{/* Main panel */}
<div className="flex flex-1 flex-col overflow-y-auto">
{/* Active call */}
{isInCall && (
<div className="space-y-4 p-5">
<ActiveCallCard lead={activeLeadFull} callerPhone={callerNumber ?? ''} />
<CallPrepCard lead={activeLeadFull} callerPhone={callerNumber ?? ''} activities={leadActivities} />
</div>
)}
{/* Worklist — no wrapper, tabs + table fill the space */}
{!isInCall && (
<WorklistPanel
missedCalls={missedCalls}
followUps={followUps}
leads={marketingLeads}
loading={loading}
onSelectLead={(lead) => setSelectedLead(lead)}
selectedLeadId={selectedLead?.id ?? null}
/>
)}
</div>
{/* Context panel — collapsible with smooth transition */}
<div className={cx(
"shrink-0 border-l border-secondary bg-primary flex flex-col overflow-hidden transition-all duration-200 ease-linear",
contextOpen ? "w-[400px]" : "w-0 border-l-0",
)}>
{contextOpen && (
<ContextPanel
selectedLead={activeLeadFull}
activities={leadActivities}
callerPhone={callerNumber ?? undefined}
/>
)}
</div>
</div>
</div>
);
};