feat: notification bell in PageHeader + remove wasted top bar row for supervisors

- PageHeader: renders NotificationBell when isAdmin — bell now appears
  on every page that uses PageHeader (leads, contacts, appointments,
  patients, call history, missed calls, call recordings, live monitor,
  team performance, settings)
- app-shell: top bar row only renders for agents (network indicator +
  status toggle). Supervisors no longer see a wasted empty row.
- Call Recordings: TopBar → PageHeader with badge + info icon
- Live Monitor: TopBar → PageHeader with badge + info icon
- Team Performance: TopBar → PageHeader with info icon
- Settings: TopBar → PageHeader with info icon
- Missed Calls: underline tabs → custom pills (consistent with all pages)
- Desktop overlay app-shell synced with same changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-17 05:51:16 +05:30
parent a9d19af1d3
commit 5f3b455edc
7 changed files with 66 additions and 58 deletions

View File

@@ -8,7 +8,6 @@ import { useSip } from '@/providers/sip-provider';
import { CallWidget } from '@/components/call-desk/call-widget'; import { CallWidget } from '@/components/call-desk/call-widget';
import { MaintOtpModal } from '@/components/modals/maint-otp-modal'; import { MaintOtpModal } from '@/components/modals/maint-otp-modal';
import { AgentStatusToggle } from '@/components/call-desk/agent-status-toggle'; import { AgentStatusToggle } from '@/components/call-desk/agent-status-toggle';
import { NotificationBell } from './notification-bell';
import { ResumeSetupBanner } from '@/components/setup/resume-setup-banner'; import { ResumeSetupBanner } from '@/components/setup/resume-setup-banner';
import { Badge } from '@/components/base/badges/badges'; import { Badge } from '@/components/base/badges/badges';
import { useAuth } from '@/providers/auth-provider'; import { useAuth } from '@/providers/auth-provider';
@@ -25,7 +24,7 @@ interface AppShellProps {
export const AppShell = ({ children }: AppShellProps) => { export const AppShell = ({ children }: AppShellProps) => {
const { pathname } = useLocation(); const { pathname } = useLocation();
const { isCCAgent, isAdmin } = useAuth(); const { isCCAgent } = useAuth();
const { isOpen, activeAction, close } = useMaintShortcuts(); const { isOpen, activeAction, close } = useMaintShortcuts();
const { connectionStatus, isRegistered } = useSip(); const { connectionStatus, isRegistered } = useSip();
const networkQuality = useNetworkStatus(); const networkQuality = useNetworkStatus();
@@ -118,18 +117,10 @@ export const AppShell = ({ children }: AppShellProps) => {
<div className="flex h-screen bg-primary"> <div className="flex h-screen bg-primary">
<Sidebar activeUrl={pathname} /> <Sidebar activeUrl={pathname} />
<div className="flex flex-1 flex-col overflow-hidden"> <div className="flex flex-1 flex-col overflow-hidden">
{/* Persistent top bar — visible on all pages */} {/* Agent top bar — network indicator + status toggle (agents only) */}
{(hasAgentConfig || isAdmin) && (
<div className="flex shrink-0 items-center gap-2 border-b border-secondary px-4 py-2">
{/* GlobalSearch hidden — navigation on result click
routes to Patient 360 with stale appointment state
from the call desk. Revisit when the Patient 360
route properly resets context on mount. (#4) */}
{/* <GlobalSearch /> */}
<div className="ml-auto flex items-center gap-2">
{isAdmin && <NotificationBell />}
{hasAgentConfig && ( {hasAgentConfig && (
<> <div className="flex shrink-0 items-center gap-2 border-b border-secondary px-4 py-2">
<div className="ml-auto flex items-center gap-2">
<div className={cx( <div className={cx(
'flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium', 'flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium',
networkQuality === 'good' networkQuality === 'good'
@@ -145,8 +136,6 @@ export const AppShell = ({ children }: AppShellProps) => {
{networkQuality === 'good' ? 'Connected' : networkQuality === 'offline' ? 'No connection' : 'Unstable'} {networkQuality === 'good' ? 'Connected' : networkQuality === 'offline' ? 'No connection' : 'Unstable'}
</div> </div>
<AgentStatusToggle isRegistered={isRegistered} connectionStatus={connectionStatus} /> <AgentStatusToggle isRegistered={isRegistered} connectionStatus={connectionStatus} />
</>
)}
</div> </div>
</div> </div>
)} )}

View File

@@ -20,6 +20,8 @@
import { useState, useRef, useEffect, type ReactNode } from 'react'; import { useState, useRef, useEffect, type ReactNode } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCircleInfo } from '@fortawesome/pro-duotone-svg-icons'; import { faCircleInfo } from '@fortawesome/pro-duotone-svg-icons';
import { NotificationBell } from './notification-bell';
import { useAuth } from '@/providers/auth-provider';
interface PageHeaderProps { interface PageHeaderProps {
title: string; title: string;
@@ -65,7 +67,9 @@ const InfoTooltip = ({ text }: { text: string }) => {
); );
}; };
export const PageHeader = ({ title, badge, subtitle, infoText, controls, tabs }: PageHeaderProps) => ( export const PageHeader = ({ title, badge, subtitle, infoText, controls, tabs }: PageHeaderProps) => {
const { isAdmin } = useAuth();
return (
<div className="shrink-0"> <div className="shrink-0">
{/* Row 1: title + controls */} {/* Row 1: title + controls */}
<div className="flex items-center justify-between px-6 py-3"> <div className="flex items-center justify-between px-6 py-3">
@@ -81,11 +85,10 @@ export const PageHeader = ({ title, badge, subtitle, infoText, controls, tabs }:
)} )}
{infoText && <InfoTooltip text={infoText} />} {infoText && <InfoTooltip text={infoText} />}
</div> </div>
{controls && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{controls} {controls}
{isAdmin && <NotificationBell />}
</div> </div>
)}
</div> </div>
{/* Row 2: optional tabs — no container border, tab underline is the separator */} {/* Row 2: optional tabs — no container border, tab underline is the separator */}
@@ -96,3 +99,4 @@ export const PageHeader = ({ title, badge, subtitle, infoText, controls, tabs }:
)} )}
</div> </div>
); );
};

View File

@@ -9,7 +9,7 @@ import { Badge } from '@/components/base/badges/badges';
import { Input } from '@/components/base/input/input'; import { Input } from '@/components/base/input/input';
import { Table } from '@/components/application/table/table'; import { Table } from '@/components/application/table/table';
import { PaginationPageDefault } from '@/components/application/pagination/pagination'; import { PaginationPageDefault } from '@/components/application/pagination/pagination';
import { TopBar } from '@/components/layout/top-bar'; import { PageHeader } from '@/components/layout/page-header';
import { ColumnToggle, useColumnVisibility } from '@/components/application/table/column-toggle'; import { ColumnToggle, useColumnVisibility } from '@/components/application/table/column-toggle';
import { PhoneActionCell } from '@/components/call-desk/phone-action-cell'; import { PhoneActionCell } from '@/components/call-desk/phone-action-cell';
import { RecordingAnalysisSlideout } from '@/components/call-desk/recording-analysis'; import { RecordingAnalysisSlideout } from '@/components/call-desk/recording-analysis';
@@ -239,18 +239,19 @@ export const CallRecordingsPage = () => {
return ( return (
<> <>
<TopBar title="Call Recordings" /> <PageHeader
<div className="flex flex-1 flex-col overflow-hidden"> title="Call Recordings"
{/* Toolbar */} badge={filtered.length}
<div className="flex shrink-0 items-center justify-between border-b border-secondary px-6 py-3"> infoText="All call recordings with AI analysis, dispositions, and playback."
<span className="text-sm text-tertiary">{filtered.length} recordings</span> controls={
<div className="flex items-center gap-3"> <>
<ColumnToggle columns={columnDefs} visibleColumns={visibleColumns} onToggle={toggleColumn} /> <ColumnToggle columns={columnDefs} visibleColumns={visibleColumns} onToggle={toggleColumn} />
<div className="w-56"> <div className="w-56">
<Input placeholder="Search agent, phone, disposition..." icon={SearchLg} size="sm" value={search} onChange={handleSearch} /> <Input placeholder="Search agent, phone, disposition..." icon={SearchLg} size="sm" value={search} onChange={handleSearch} />
</div> </div>
</div> </>
</div> }
/>
{/* Table */} {/* Table */}
<div className="flex flex-1 flex-col min-h-0 overflow-hidden px-4 pt-3"> <div className="flex flex-1 flex-col min-h-0 overflow-hidden px-4 pt-3">

View File

@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faHeadset, faPhoneVolume, faPause, faClock, faSparkles, faCalendarCheck, faClockRotateLeft } from '@fortawesome/pro-duotone-svg-icons'; import { faHeadset, faPhoneVolume, faPause, faClock, faSparkles, faCalendarCheck, faClockRotateLeft } from '@fortawesome/pro-duotone-svg-icons';
import { TopBar } from '@/components/layout/top-bar'; import { PageHeader } from '@/components/layout/page-header';
import { Badge } from '@/components/base/badges/badges'; import { Badge } from '@/components/base/badges/badges';
import { Table } from '@/components/application/table/table'; import { Table } from '@/components/application/table/table';
import { BargeControls } from '@/components/call-desk/barge-controls'; import { BargeControls } from '@/components/call-desk/barge-controls';
@@ -182,7 +182,11 @@ export const LiveMonitorPage = () => {
return ( return (
<> <>
<TopBar title="Live Call Monitor" subtitle="Monitor, whisper, or barge into active calls" /> <PageHeader
title="Live Call Monitor"
badge={activeCalls.length}
infoText="Monitor, whisper, or barge into active calls in real-time."
/>
<div className="flex flex-1 overflow-hidden"> <div className="flex flex-1 overflow-hidden">
{/* Left panel — KPIs + call list */} {/* Left panel — KPIs + call list */}

View File

@@ -7,7 +7,6 @@ const SearchLg = faIcon(faMagnifyingGlass);
import { Badge } from '@/components/base/badges/badges'; import { Badge } from '@/components/base/badges/badges';
import { Input } from '@/components/base/input/input'; import { Input } from '@/components/base/input/input';
import { Table } from '@/components/application/table/table'; import { Table } from '@/components/application/table/table';
import { Tabs, TabList, Tab } from '@/components/application/tabs/tabs';
import { PaginationPageDefault } from '@/components/application/pagination/pagination'; import { PaginationPageDefault } from '@/components/application/pagination/pagination';
import { PageHeader } from '@/components/layout/page-header'; import { PageHeader } from '@/components/layout/page-header';
import { ColumnToggle, useColumnVisibility } from '@/components/application/table/column-toggle'; import { ColumnToggle, useColumnVisibility } from '@/components/application/table/column-toggle';
@@ -253,11 +252,22 @@ export const MissedCallsPage = () => {
</> </>
} }
tabs={ tabs={
<Tabs selectedKey={tab} onSelectionChange={(key) => handleTab(key as StatusTab)}> <div className="flex items-center gap-1.5">
<TabList items={tabItems} type="underline" size="sm"> {tabItems.map((item) => (
{(item) => <Tab key={item.id} id={item.id} label={item.label} badge={item.badge} />} <button
</TabList> key={item.id}
</Tabs> onClick={() => handleTab(item.id)}
className={cx(
'shrink-0 rounded-full px-3 py-1 text-xs font-medium transition duration-100 ease-linear',
tab === item.id
? 'bg-brand-solid text-white'
: 'bg-secondary text-tertiary hover:text-secondary hover:bg-secondary_hover',
)}
>
{item.label}{item.badge ? ` ${item.badge}` : ''}
</button>
))}
</div>
} }
/> />

View File

@@ -9,7 +9,7 @@ import {
faPalette, faPalette,
faShieldHalved, faShieldHalved,
} from '@fortawesome/pro-duotone-svg-icons'; } from '@fortawesome/pro-duotone-svg-icons';
import { TopBar } from '@/components/layout/top-bar'; import { PageHeader } from '@/components/layout/page-header';
import { SectionCard } from '@/components/setup/section-card'; import { SectionCard } from '@/components/setup/section-card';
import { import {
SETUP_STEP_NAMES, SETUP_STEP_NAMES,
@@ -50,7 +50,7 @@ export const SettingsPage = () => {
return ( return (
<div className="flex flex-1 flex-col overflow-hidden"> <div className="flex flex-1 flex-col overflow-hidden">
<TopBar title="Settings" subtitle="Configure your hospital workspace" /> <PageHeader title="Settings" infoText="Configure your hospital workspace." />
<div className="flex-1 overflow-y-auto p-8"> <div className="flex-1 overflow-y-auto p-8">
<div className="mx-auto max-w-5xl"> <div className="mx-auto max-w-5xl">

View File

@@ -5,7 +5,7 @@ import {
faUsers, faPhoneVolume, faCalendarCheck, faPhoneMissed, faUsers, faPhoneVolume, faCalendarCheck, faPhoneMissed,
faPercent, faTriangleExclamation, faPercent, faTriangleExclamation,
} from '@fortawesome/pro-duotone-svg-icons'; } from '@fortawesome/pro-duotone-svg-icons';
import { TopBar } from '@/components/layout/top-bar'; import { PageHeader } from '@/components/layout/page-header';
import { Badge } from '@/components/base/badges/badges'; import { Badge } from '@/components/base/badges/badges';
import { Table } from '@/components/application/table/table'; import { Table } from '@/components/application/table/table';
import { apiClient } from '@/lib/api-client'; import { apiClient } from '@/lib/api-client';
@@ -292,7 +292,7 @@ export const TeamPerformancePage = () => {
if (loading) { if (loading) {
return ( return (
<> <>
<TopBar title="Team Performance" /> <PageHeader title="Team Performance" infoText="Aggregated metrics across all agents." />
<div className="flex flex-1 items-center justify-center"> <div className="flex flex-1 items-center justify-center">
<p className="text-sm text-tertiary">Loading team performance...</p> <p className="text-sm text-tertiary">Loading team performance...</p>
</div> </div>
@@ -302,7 +302,7 @@ export const TeamPerformancePage = () => {
return ( return (
<> <>
<TopBar title="Team Performance" subtitle="Aggregated metrics across all agents" /> <PageHeader title="Team Performance" infoText="Aggregated metrics across all agents." />
<div className="flex flex-1 flex-col overflow-y-auto"> <div className="flex flex-1 flex-col overflow-y-auto">
{/* Section 1: Key Metrics */} {/* Section 1: Key Metrics */}