feat: disposition modal, persistent top bar, pagination, QA fixes

- DispositionModal: single modal for all call endings. Dismissable (agent can resume call).
  Agent clicks End → modal → select reason → hangup + dispose. Caller disconnects → same modal.
- One call screen: CallWidget stripped to ringing notification + auto-redirect to Call Desk.
- Persistent top bar in AppShell: agent status toggle + network indicator on all pages.
- Network indicator always visible (Connected/Unstable/No connection).
- Pagination: Untitled UI PaginationCardDefault on Call History + Appointments (20/page).
- Pinned table headers/footers: sticky column headers, scrollable body, pinned pagination.
  Applied to Call Desk worklist, Call History, Appointments, Call Recordings, Missed Calls.
- "Patient" → "Caller" column label in Call History.
- Offline → Ready toggle enabled.
- Profile status dot reflects Ozonetel state.
- NavAccountCard: popover placement top, View Profile + Account Settings restored.
- WIP pages for /profile and /account-settings.
- Enquiry form PHONE_INQUIRY → PHONE enum fix.
- Force Ready / View Profile / Account Settings removed then restored properly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 20:29:54 +05:30
parent daa2fbb0c2
commit e6b2208077
21 changed files with 645 additions and 816 deletions

View File

@@ -13,8 +13,6 @@ import {
faCalendarCheck,
faPhone,
faUsers,
faWifi,
faWifiSlash,
faArrowRightFromBracket,
faTowerBroadcast,
faChartLine,
@@ -31,10 +29,8 @@ import { NavAccountCard } from "@/components/application/app-navigation/base-com
import { NavItemBase } from "@/components/application/app-navigation/base-components/nav-item";
import type { NavItemType } from "@/components/application/app-navigation/config";
import { Avatar } from "@/components/base/avatar/avatar";
import { apiClient } from "@/lib/api-client";
import { notify } from "@/lib/toast";
import { useAuth } from "@/providers/auth-provider";
import { useNetworkStatus } from "@/hooks/use-network-status";
import { useAgentState } from "@/hooks/use-agent-state";
import { sidebarCollapsedAtom } from "@/state/sidebar-state";
import { cx } from "@/utils/cx";
@@ -126,7 +122,10 @@ export const Sidebar = ({ activeUrl = "/" }: SidebarProps) => {
const { logout, user } = useAuth();
const navigate = useNavigate();
const [collapsed, setCollapsed] = useAtom(sidebarCollapsedAtom);
const networkQuality = useNetworkStatus();
const agentConfig = typeof window !== 'undefined' ? localStorage.getItem('helix_agent_config') : null;
const agentId = agentConfig ? (() => { try { return JSON.parse(agentConfig).ozonetelAgentId; } catch { return null; } })() : null;
const ozonetelState = useAgentState(agentId);
const avatarStatus: 'online' | 'offline' = ozonetelState === 'ready' ? 'online' : 'offline';
const width = collapsed ? COLLAPSED_WIDTH : EXPANDED_WIDTH;
@@ -142,15 +141,6 @@ export const Sidebar = ({ activeUrl = "/" }: SidebarProps) => {
navigate('/login');
};
const handleForceReady = async () => {
try {
await apiClient.post('/api/ozonetel/agent-ready', {});
notify.success('Agent Ready', 'Agent state has been reset to Ready');
} catch {
notify.error('Force Ready Failed', 'Could not reset agent state');
}
};
const navSections = getNavSections(user.role);
const content = (
@@ -222,25 +212,6 @@ export const Sidebar = ({ activeUrl = "/" }: SidebarProps) => {
))}
</ul>
{/* Network indicator — only shows when network is degraded */}
{networkQuality !== 'good' && (
<div className={cx(
"mx-3 mb-2 flex items-center gap-2 rounded-lg px-3 py-2 text-xs font-medium",
networkQuality === 'offline'
? "bg-error-secondary text-error-primary"
: "bg-warning-secondary text-warning-primary",
collapsed && "justify-center mx-2 px-2",
)}>
<FontAwesomeIcon
icon={networkQuality === 'offline' ? faWifiSlash : faWifi}
className="size-3.5 shrink-0"
/>
{!collapsed && (
<span>{networkQuality === 'offline' ? 'No connection' : 'Unstable network'}</span>
)}
</div>
)}
{/* Account card */}
<div className={cx("mt-auto py-4", collapsed ? "flex justify-center px-2" : "px-2 lg:px-4")}>
{collapsed ? (
@@ -249,7 +220,7 @@ export const Sidebar = ({ activeUrl = "/" }: SidebarProps) => {
title={`${user.name}\nSign out`}
className="rounded-lg p-1 hover:bg-primary_hover transition duration-100 ease-linear"
>
<Avatar size="sm" initials={user.initials} status="online" />
<Avatar size="sm" initials={user.initials} status={avatarStatus} />
</button>
) : (
<NavAccountCard
@@ -258,11 +229,13 @@ export const Sidebar = ({ activeUrl = "/" }: SidebarProps) => {
name: user.name,
email: user.email,
avatar: '',
status: 'online' as const,
status: avatarStatus,
}]}
selectedAccountId="current"
popoverPlacement="top"
onSignOut={handleSignOut}
onForceReady={handleForceReady}
onViewProfile={() => navigate('/profile')}
onAccountSettings={() => navigate('/account-settings')}
/>
)}
</div>