mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
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>
This commit is contained in:
@@ -5,36 +5,21 @@ 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 { TopBar } from '@/components/layout/top-bar';
|
||||
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 { CallLog } from '@/components/call-desk/call-log';
|
||||
import { BadgeWithDot, Badge } from '@/components/base/badges/badges';
|
||||
import { cx } from '@/utils/cx';
|
||||
|
||||
type MainTab = 'worklist' | 'calls';
|
||||
|
||||
const isToday = (dateStr: string): boolean => {
|
||||
const d = new Date(dateStr);
|
||||
const now = new Date();
|
||||
return d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate();
|
||||
};
|
||||
|
||||
export const CallDeskPage = () => {
|
||||
const { user } = useAuth();
|
||||
const { calls, leadActivities } = useData();
|
||||
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 [mainTab, setMainTab] = useState<MainTab>('worklist');
|
||||
|
||||
const todaysCalls = calls.filter(
|
||||
(c) => c.agentName === user.name && c.startedAt !== null && isToday(c.startedAt),
|
||||
);
|
||||
|
||||
const isInCall = callState === 'ringing-in' || callState === 'ringing-out' || callState === 'active';
|
||||
|
||||
@@ -47,10 +32,13 @@ export const CallDeskPage = () => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
<TopBar title="Call Desk" subtitle={`${user.name} \u00B7 Global Hospital`} />
|
||||
{/* 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>
|
||||
|
||||
{/* Status bar */}
|
||||
<div className="flex shrink-0 items-center justify-between border-b border-secondary px-6 py-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<BadgeWithDot
|
||||
color={isRegistered ? 'success' : connectionStatus === 'connecting' ? 'warning' : 'gray'}
|
||||
@@ -62,37 +50,9 @@ export const CallDeskPage = () => {
|
||||
{totalPending > 0 && (
|
||||
<Badge size="sm" color="brand" type="pill-color">{totalPending} pending</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Main tab toggle */}
|
||||
{!isInCall && (
|
||||
<div className="flex rounded-lg border border-secondary">
|
||||
<button
|
||||
onClick={() => setMainTab('worklist')}
|
||||
className={cx(
|
||||
"px-3 py-1 text-xs font-semibold transition duration-100 ease-linear rounded-l-lg",
|
||||
mainTab === 'worklist' ? "bg-active text-brand-secondary" : "text-tertiary hover:text-secondary",
|
||||
)}
|
||||
>
|
||||
Worklist
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setMainTab('calls')}
|
||||
className={cx(
|
||||
"px-3 py-1 text-xs font-semibold transition duration-100 ease-linear rounded-r-lg",
|
||||
mainTab === 'calls' ? "bg-active text-brand-secondary" : "text-tertiary hover:text-secondary",
|
||||
)}
|
||||
>
|
||||
Today's Calls ({todaysCalls.length})
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Context panel toggle */}
|
||||
<button
|
||||
onClick={() => setContextOpen(!contextOpen)}
|
||||
className="flex size-7 items-center justify-center rounded-md text-fg-quaternary hover:text-fg-secondary hover:bg-secondary transition duration-100 ease-linear"
|
||||
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" />
|
||||
@@ -102,46 +62,42 @@ export const CallDeskPage = () => {
|
||||
|
||||
{/* Main content */}
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* Main panel — expands when context is closed */}
|
||||
{/* Main panel */}
|
||||
<div className="flex flex-1 flex-col overflow-y-auto">
|
||||
<div className="flex-1 p-5">
|
||||
{/* Active call */}
|
||||
{isInCall && (
|
||||
<div className="space-y-4">
|
||||
<ActiveCallCard lead={activeLeadFull} callerPhone={callerNumber ?? ''} />
|
||||
<CallPrepCard lead={activeLeadFull} callerPhone={callerNumber ?? ''} activities={leadActivities} />
|
||||
</div>
|
||||
)}
|
||||
{/* Active call */}
|
||||
{isInCall && (
|
||||
<div className="space-y-4 p-5">
|
||||
<ActiveCallCard lead={activeLeadFull} callerPhone={callerNumber ?? ''} />
|
||||
<CallPrepCard lead={activeLeadFull} callerPhone={callerNumber ?? ''} activities={leadActivities} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Worklist tab */}
|
||||
{!isInCall && mainTab === 'worklist' && (
|
||||
<WorklistPanel
|
||||
missedCalls={missedCalls}
|
||||
followUps={followUps}
|
||||
leads={marketingLeads}
|
||||
loading={loading}
|
||||
onSelectLead={(lead) => setSelectedLead(lead)}
|
||||
selectedLeadId={selectedLead?.id ?? null}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Today's Calls tab */}
|
||||
{!isInCall && mainTab === 'calls' && (
|
||||
<CallLog calls={todaysCalls} />
|
||||
)}
|
||||
</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 */}
|
||||
{contextOpen && (
|
||||
<div className="w-[400px] shrink-0 border-l border-secondary bg-primary flex flex-col">
|
||||
{/* 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>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user