mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 10:23:27 +00:00
- Import wizard: added step indicator (numbered circles), widened to max-w-5xl - Admin sidebar: added Marketing → Campaigns nav link - Clear campaign leads: Ctrl+Shift+C shortcut with campaign picker modal (test-only) - Test CSV data for all 3 campaigns - Defect fixing plan + CSV import spec docs - Session memory update Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
88 lines
4.5 KiB
TypeScript
88 lines
4.5 KiB
TypeScript
import { useEffect, type ReactNode } from 'react';
|
|
import { useLocation } from 'react-router';
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
import { faWifi, faWifiSlash } from '@fortawesome/pro-duotone-svg-icons';
|
|
import { Sidebar } from './sidebar';
|
|
import { SipProvider } from '@/providers/sip-provider';
|
|
import { useSip } from '@/providers/sip-provider';
|
|
import { CallWidget } from '@/components/call-desk/call-widget';
|
|
import { MaintOtpModal } from '@/components/modals/maint-otp-modal';
|
|
import { ClearCampaignLeadsModal } from '@/components/modals/clear-campaign-leads-modal';
|
|
import { AgentStatusToggle } from '@/components/call-desk/agent-status-toggle';
|
|
import { NotificationBell } from './notification-bell';
|
|
import { useAuth } from '@/providers/auth-provider';
|
|
import { useMaintShortcuts } from '@/hooks/use-maint-shortcuts';
|
|
import { useNetworkStatus } from '@/hooks/use-network-status';
|
|
import { cx } from '@/utils/cx';
|
|
|
|
interface AppShellProps {
|
|
children: ReactNode;
|
|
}
|
|
|
|
export const AppShell = ({ children }: AppShellProps) => {
|
|
const { pathname } = useLocation();
|
|
const { isCCAgent, isAdmin } = useAuth();
|
|
const { isOpen, activeAction, close } = useMaintShortcuts();
|
|
const { connectionStatus, isRegistered } = useSip();
|
|
const networkQuality = useNetworkStatus();
|
|
const hasAgentConfig = !!localStorage.getItem('helix_agent_config');
|
|
|
|
// Heartbeat: keep agent session alive in Redis (CC agents only)
|
|
useEffect(() => {
|
|
if (!isCCAgent) return;
|
|
|
|
const beat = () => {
|
|
const token = localStorage.getItem('helix_access_token');
|
|
if (token) {
|
|
const apiUrl = import.meta.env.VITE_API_URL ?? 'http://localhost:4100';
|
|
fetch(`${apiUrl}/auth/heartbeat`, {
|
|
method: 'POST',
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
}).catch(() => {});
|
|
}
|
|
};
|
|
|
|
const interval = setInterval(beat, 5 * 60 * 1000);
|
|
return () => clearInterval(interval);
|
|
}, [isCCAgent]);
|
|
|
|
return (
|
|
<SipProvider>
|
|
<div className="flex h-screen bg-primary">
|
|
<Sidebar activeUrl={pathname} />
|
|
<div className="flex flex-1 flex-col overflow-hidden">
|
|
{/* Persistent top bar — visible on all pages */}
|
|
{(hasAgentConfig || isAdmin) && (
|
|
<div className="flex shrink-0 items-center justify-end gap-2 border-b border-secondary px-4 py-2">
|
|
{isAdmin && <NotificationBell />}
|
|
{hasAgentConfig && (
|
|
<>
|
|
<div className={cx(
|
|
'flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium',
|
|
networkQuality === 'good'
|
|
? 'bg-success-primary text-success-primary'
|
|
: networkQuality === 'offline'
|
|
? 'bg-error-secondary text-error-primary'
|
|
: 'bg-warning-secondary text-warning-primary',
|
|
)}>
|
|
<FontAwesomeIcon
|
|
icon={networkQuality === 'offline' ? faWifiSlash : faWifi}
|
|
className="size-3"
|
|
/>
|
|
{networkQuality === 'good' ? 'Connected' : networkQuality === 'offline' ? 'No connection' : 'Unstable'}
|
|
</div>
|
|
<AgentStatusToggle isRegistered={isRegistered} connectionStatus={connectionStatus} />
|
|
</>
|
|
)}
|
|
</div>
|
|
)}
|
|
<main className="flex flex-1 flex-col overflow-hidden">{children}</main>
|
|
</div>
|
|
{isCCAgent && pathname !== '/' && pathname !== '/call-desk' && <CallWidget />}
|
|
</div>
|
|
<MaintOtpModal isOpen={isOpen && activeAction?.endpoint !== '__client__clear-campaign-leads'} onOpenChange={(open) => !open && close()} action={activeAction} />
|
|
<ClearCampaignLeadsModal isOpen={isOpen && activeAction?.endpoint === '__client__clear-campaign-leads'} onOpenChange={(open) => !open && close()} />
|
|
</SipProvider>
|
|
);
|
|
};
|