feat: info icon on all PageHeader pages + Call Desk header restyled

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-16 21:49:00 +05:30
parent dfcaa175ab
commit 313842a922
8 changed files with 64 additions and 12 deletions

View File

@@ -1,29 +1,71 @@
// PageHeader — consistent header layout for all list pages. // PageHeader — consistent header layout for all list pages.
// //
// Row 1: Title (+ optional badge + subtitle) on the left, // Row 1: Title (+ optional badge + info icon) on the left,
// controls (search, columns, export, etc.) on the right. // controls (search, columns, export, etc.) on the right.
// Row 2: Optional tabs (underline style) — no extra borders. // Row 2: Optional tabs (underline style) — no extra borders.
// //
// The `infoText` prop renders as a hoverable info icon (ⓘ) next to
// the title. Long descriptive text goes here instead of inline
// subtitle — keeps the header compact.
//
// Usage: // Usage:
// <PageHeader // <PageHeader
// title="Appointments" // title="Contacts"
// badge={37} // badge={16}
// subtitle="Manage appointments" // infoText="People who reached out directly — phone, walk-in, referral."
// controls={<><Input .../> <Button .../></>} // controls={<><Input .../> <Button .../></>}
// tabs={<Tabs ...><TabList ...>{...}</TabList></Tabs>} // tabs={<Tabs ...><TabList ...>{...}</TabList></Tabs>}
// /> // />
import type { ReactNode } from 'react'; import { useState, useRef, useEffect, type ReactNode } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCircleInfo } from '@fortawesome/pro-duotone-svg-icons';
interface PageHeaderProps { interface PageHeaderProps {
title: string; title: string;
badge?: number | string; badge?: number | string;
/** Short inline text next to badge — use sparingly (e.g. "17 total") */
subtitle?: string; subtitle?: string;
/** Longer descriptive text shown on info icon hover/click */
infoText?: string;
controls?: ReactNode; controls?: ReactNode;
tabs?: ReactNode; tabs?: ReactNode;
} }
export const PageHeader = ({ title, badge, subtitle, controls, tabs }: PageHeaderProps) => ( const InfoTooltip = ({ text }: { text: string }) => {
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!open) return;
const handler = (e: MouseEvent) => {
if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);
};
document.addEventListener('mousedown', handler);
return () => document.removeEventListener('mousedown', handler);
}, [open]);
return (
<div className="relative" ref={ref}>
<button
onClick={() => setOpen(!open)}
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
className="flex size-5 items-center justify-center rounded-full text-fg-quaternary hover:text-fg-secondary transition duration-100 ease-linear"
title={text}
>
<FontAwesomeIcon icon={faCircleInfo} className="size-4" />
</button>
{open && (
<div className="absolute left-0 top-full mt-1 z-50 w-72 rounded-lg bg-primary px-3 py-2 text-xs text-tertiary shadow-lg ring-1 ring-secondary">
{text}
</div>
)}
</div>
);
};
export const PageHeader = ({ title, badge, subtitle, infoText, controls, tabs }: PageHeaderProps) => (
<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">
@@ -37,6 +79,7 @@ export const PageHeader = ({ title, badge, subtitle, controls, tabs }: PageHeade
{subtitle && ( {subtitle && (
<span className="text-sm text-tertiary ml-1">{subtitle}</span> <span className="text-sm text-tertiary ml-1">{subtitle}</span>
)} )}
{infoText && <InfoTooltip text={infoText} />}
</div> </div>
{controls && ( {controls && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">

View File

@@ -246,6 +246,7 @@ export const AllLeadsPage = () => {
<PageHeader <PageHeader
title="All Leads" title="All Leads"
subtitle={`${total} total`} subtitle={`${total} total`}
infoText="Campaign-sourced marketing leads. Organic callers are on the Contacts page."
controls={ controls={
<> <>
<div className="w-56"> <div className="w-56">

View File

@@ -555,6 +555,7 @@ export const AppointmentsPageV2 = () => {
<PageHeader <PageHeader
title="Appointments" title="Appointments"
badge={filtered.length} badge={filtered.length}
infoText="All scheduled, completed, cancelled, and rescheduled appointments. Click the eye icon to view details or reschedule."
controls={ controls={
<div className="w-56"> <div className="w-56">
<Input <Input

View File

@@ -16,6 +16,8 @@ import { ActiveCallCard } from '@/components/call-desk/active-call-card';
import { apiClient } from '@/lib/api-client'; import { apiClient } from '@/lib/api-client';
import { notify } from '@/lib/toast'; import { notify } from '@/lib/toast';
import { cx } from '@/utils/cx'; import { cx } from '@/utils/cx';
import { FontAwesomeIcon as FAIcon } from '@fortawesome/react-fontawesome';
import { faCircleInfo } from '@fortawesome/pro-duotone-svg-icons';
export const CallDeskPage = () => { export const CallDeskPage = () => {
const { user } = useAuth(); const { user } = useAuth();
@@ -166,11 +168,14 @@ export const CallDeskPage = () => {
return ( return (
<div className="flex flex-1 flex-col overflow-hidden"> <div className="flex flex-1 flex-col overflow-hidden">
{/* Compact header: title + name on left, status + toggle on right */} {/* Header — matches PageHeader visual pattern */}
<div className="flex shrink-0 items-center justify-between border-b border-secondary px-6 py-3"> <div className="flex shrink-0 items-center justify-between px-6 py-3">
<div className="flex items-center gap-3"> <div className="flex items-center gap-2">
<h1 className="text-lg font-bold text-primary">Call Desk</h1> <h1 className="text-lg font-bold text-primary">Call Desk</h1>
<span className="text-sm text-tertiary">{user.name}</span> <span className="text-sm text-tertiary ml-1">{user.name}</span>
<span className="flex size-5 items-center justify-center text-fg-quaternary" title="Your active worklist — missed calls, leads, and follow-ups prioritised by SLA.">
<FAIcon icon={faCircleInfo} className="size-4" />
</span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">

View File

@@ -194,6 +194,7 @@ export const CallHistoryPage = () => {
title={isAdmin ? 'Call History' : 'My Call History'} title={isAdmin ? 'Call History' : 'My Call History'}
badge={filteredCalls.length} badge={filteredCalls.length}
subtitle={isAdmin ? `${completedCount} completed \u00B7 ${missedCount} missed` : `${completedCount} completed`} subtitle={isAdmin ? `${completedCount} completed \u00B7 ${missedCount} missed` : `${completedCount} completed`}
infoText={isAdmin ? 'All calls across all agents with recordings and dispositions.' : 'Your answered inbound and outbound calls.'}
controls={ controls={
<> <>
<div className="w-44"> <div className="w-44">

View File

@@ -116,7 +116,7 @@ export const ContactsPage = () => {
<PageHeader <PageHeader
title="Contacts" title="Contacts"
badge={contacts.length} badge={contacts.length}
subtitle="People who reached out directly — phone, walk-in, referral. Not sourced from campaigns." infoText="People who reached out directly — phone, walk-in, referral. Not sourced from campaigns."
controls={ controls={
<> <>
<div className="w-56"> <div className="w-56">

View File

@@ -242,6 +242,7 @@ export const MissedCallsPage = () => {
<PageHeader <PageHeader
title="Missed Calls" title="Missed Calls"
badge={calls.length} badge={calls.length}
infoText="Inbound calls that were not answered. Agents can call back from here."
controls={ controls={
<> <>
<ColumnToggle columns={columnDefs} visibleColumns={visibleColumns} onToggle={toggleColumn} /> <ColumnToggle columns={columnDefs} visibleColumns={visibleColumns} onToggle={toggleColumn} />

View File

@@ -131,7 +131,7 @@ export const PatientsPage = () => {
<PageHeader <PageHeader
title="All Patients" title="All Patients"
badge={filteredPatients.length} badge={filteredPatients.length}
subtitle="Manage and view patient records" infoText="Manage and view patient records"
controls={ controls={
<> <>
<button <button