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

@@ -1,4 +1,4 @@
import { useMemo, useRef, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import type { FC } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
@@ -20,6 +20,7 @@ import { TopBar } from '@/components/layout/top-bar';
import { ClickToCallButton } from '@/components/call-desk/click-to-call-button';
import { formatShortDate, formatPhone } from '@/lib/format';
import { useData } from '@/providers/data-provider';
import { PaginationCardDefault } from '@/components/application/pagination/pagination';
import type { Call, CallDirection, CallDisposition } from '@/types/entities';
type FilterKey = 'all' | 'inbound' | 'outbound' | 'missed';
@@ -103,17 +104,13 @@ const RecordingPlayer: FC<{ url: string }> = ({ url }) => {
);
};
const PAGE_SIZE = 20;
export const CallHistoryPage = () => {
const { calls, leads } = useData();
const [search, setSearch] = useState('');
const [filter, setFilter] = useState<FilterKey>('all');
// Debug: log first call's raw timestamp to diagnose timezone issue
if (calls.length > 0 && !(window as any).__callTimestampLogged) {
const c = calls[0];
console.log(`[DEBUG-TIME] Raw startedAt="${c.startedAt}" → parsed=${new Date(c.startedAt!)} → formatted="${c.startedAt ? new Intl.DateTimeFormat('en-IN', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit', hour12: true }).format(new Date(c.startedAt)) : 'n/a'}" | direction=${c.callDirection} status=${c.callStatus}`);
(window as any).__callTimestampLogged = true;
}
const [page, setPage] = useState(1);
// Build a map of lead names by ID for enrichment
const leadNameMap = useMemo(() => {
@@ -161,12 +158,18 @@ export const CallHistoryPage = () => {
const completedCount = filteredCalls.filter((c) => c.callStatus !== 'MISSED').length;
const missedCount = filteredCalls.filter((c) => c.callStatus === 'MISSED').length;
const totalPages = Math.max(1, Math.ceil(filteredCalls.length / PAGE_SIZE));
const pagedCalls = filteredCalls.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE);
// Reset page when filter/search changes
useEffect(() => { setPage(1); }, [filter, search]); // eslint-disable-line react-hooks/exhaustive-deps
return (
<div className="flex flex-1 flex-col overflow-hidden">
<TopBar title="Call History" subtitle={`${filteredCalls.length} total calls`} />
<div className="flex-1 overflow-y-auto p-7">
<TableCard.Root size="md">
<div className="flex flex-1 flex-col overflow-hidden p-7">
<TableCard.Root size="md" className="flex-1 min-h-0">
<TableCard.Header
title="Call History"
badge={String(filteredCalls.length)}
@@ -214,7 +217,7 @@ export const CallHistoryPage = () => {
<Table>
<Table.Header>
<Table.Head label="TYPE" className="w-14" isRowHeader />
<Table.Head label="PATIENT" />
<Table.Head label="CALLER" />
<Table.Head label="PHONE" />
<Table.Head label="DURATION" className="w-24" />
<Table.Head label="OUTCOME" />
@@ -223,7 +226,7 @@ export const CallHistoryPage = () => {
<Table.Head label="TIME" />
<Table.Head label="ACTIONS" className="w-24" />
</Table.Header>
<Table.Body items={filteredCalls}>
<Table.Body items={pagedCalls}>
{(call) => {
const patientName = call.leadName ?? leadNameMap.get(call.leadId ?? '') ?? 'Unknown';
const phoneDisplay = formatPhoneDisplay(call);
@@ -294,6 +297,13 @@ export const CallHistoryPage = () => {
</Table.Body>
</Table>
)}
<div className="shrink-0">
<PaginationCardDefault
page={page}
total={totalPages}
onPageChange={setPage}
/>
</div>
</TableCard.Root>
</div>
</div>