feat: consistent UI across all list pages — PhoneActionCell, custom pills, eye icon
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

- PhoneActionCell: kebab always visible (SMS + WhatsApp), Call removed from menu,
  phone number always brand-colored regardless of telephony state
- LeadTable: replaced actions kebab column with eye icon (first column) for
  view activity, phone column now uses PhoneActionCell
- Worklist: React Aria Tabs replaced with custom pill buttons matching All Leads
  pattern (bg-brand-solid on selected), search lifted to call-desk.tsx header
- Appointments: underline tabs replaced with custom pills, phone in patient cell
  uses PhoneActionCell, group/row added to rows
- Patients: removed redundant HamburgerMenu column, group/row on rows
- Call Desk: search input in header row, cleaned up duplicate imports

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-16 23:05:32 +05:30
parent 313842a922
commit bdabcb2ea4
6 changed files with 94 additions and 136 deletions

View File

@@ -12,7 +12,6 @@ import { Badge } from '@/components/base/badges/badges';
import { Input } from '@/components/base/input/input';
import { Table } from '@/components/application/table/table';
import { PaginationCardDefault } from '@/components/application/pagination/pagination';
import { Tabs, TabList, Tab } from '@/components/application/tabs/tabs';
// TopBar replaced by inline header
import { Button } from '@/components/base/buttons/button';
import { ModalOverlay, Modal, Dialog } from '@/components/application/modals/modal';
@@ -569,11 +568,22 @@ export const AppointmentsPageV2 = () => {
</div>
}
tabs={
<Tabs selectedKey={tab} onSelectionChange={(key) => setTab(key as StatusTab)}>
<TabList items={tabItems} type="underline" size="sm">
{(item) => <Tab key={item.id} id={item.id} label={item.label} badge={item.badge} />}
</TabList>
</Tabs>
<div className="flex items-center gap-1.5">
{tabItems.map((item) => (
<button
key={item.id}
onClick={() => setTab(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>
}
/>
@@ -611,7 +621,7 @@ export const AppointmentsPageV2 = () => {
return (
<Table.Row
id={appt.id}
className={cx(isSelected && 'bg-brand-primary')}
className={cx('group/row', isSelected && 'bg-brand-primary')}
>
{/* Eye icon — first column */}
<Table.Cell>
@@ -628,7 +638,7 @@ export const AppointmentsPageV2 = () => {
<Table.Cell>
<div className="min-w-0">
<p className="text-sm font-medium text-primary truncate">{name}</p>
{phone && <p className="text-xs text-tertiary">{formatPhone({ number: phone, callingCode: '+91' })}</p>}
{phone && <PhoneActionCell phoneNumber={phone} displayNumber={formatPhone({ number: phone, callingCode: '+91' })} />}
</div>
</Table.Cell>

View File

@@ -1,6 +1,6 @@
import { useState, useEffect, useRef, useCallback } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSidebarFlip, faSidebar, faPhone, faXmark, faDeleteLeft, faFlask } from '@fortawesome/pro-duotone-svg-icons';
import { faSidebarFlip, faSidebar, faPhone, faXmark, faDeleteLeft, faFlask, faMagnifyingGlass, faCircleInfo } from '@fortawesome/pro-duotone-svg-icons';
import { useSetAtom } from 'jotai';
import { sipCallStateAtom, sipCallerNumberAtom, sipCallUcidAtom, sipCallDurationAtom } from '@/state/sip-state';
import { useAuth } from '@/providers/auth-provider';
@@ -12,12 +12,14 @@ import type { WorklistSelection } from '@/components/call-desk/worklist-panel';
import { ContextPanel } from '@/components/call-desk/context-panel';
import type { ContextPanelSubject } from '@/components/call-desk/context-panel';
import { ActiveCallCard } from '@/components/call-desk/active-call-card';
import { Input } from '@/components/base/input/input';
import { faIcon } from '@/lib/icon-wrapper';
import { apiClient } from '@/lib/api-client';
import { notify } from '@/lib/toast';
import { cx } from '@/utils/cx';
import { FontAwesomeIcon as FAIcon } from '@fortawesome/react-fontawesome';
import { faCircleInfo } from '@fortawesome/pro-duotone-svg-icons';
const SearchLg = faIcon(faMagnifyingGlass);
export const CallDeskPage = () => {
const { user } = useAuth();
@@ -32,6 +34,7 @@ export const CallDeskPage = () => {
const [diallerOpen, setDiallerOpen] = useState(false);
const [dialNumber, setDialNumber] = useState('');
const [dialling, setDialling] = useState(false);
const [search, setSearch] = useState('');
// DEV: simulate incoming call
const setSimCallState = useSetAtom(sipCallStateAtom);
@@ -174,11 +177,23 @@ export const CallDeskPage = () => {
<h1 className="text-lg font-bold text-primary">Call Desk</h1>
<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" />
<FontAwesomeIcon icon={faCircleInfo} className="size-4" />
</span>
</div>
<div className="flex items-center gap-2">
{!isInCall && (
<div className="w-52">
<Input
placeholder="Search worklist..."
icon={SearchLg}
size="sm"
value={search}
onChange={setSearch}
aria-label="Search worklist"
/>
</div>
)}
{import.meta.env.DEV && (!isInCall ? (
<button
onClick={startSimCall}
@@ -288,6 +303,7 @@ export const CallDeskPage = () => {
onSelectItem={handleSelectItem}
selectedItemId={selectedItemId}
onDialMissedCall={(id) => setActiveMissedCallId(id)}
search={search}
/>
)}
</div>

View File

@@ -1,7 +1,7 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { useMemo, useState } from 'react';
// useNavigate removed — row click opens profile panel
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faUser, faMagnifyingGlass, faCommentDots, faMessageDots, faEllipsisVertical, faSidebarFlip, faSidebar } from '@fortawesome/pro-duotone-svg-icons';
import { faUser, faMagnifyingGlass, faSidebarFlip, faSidebar } from '@fortawesome/pro-duotone-svg-icons';
import { faIcon } from '@/lib/icon-wrapper';
const SearchLg = faIcon(faMagnifyingGlass);
@@ -55,49 +55,6 @@ const getPatientEmail = (patient: Patient): string => {
return patient.emails?.primaryEmail ?? '';
};
const HamburgerMenu = ({ phone }: { phone: 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={(e) => { e.stopPropagation(); setOpen(!open); }}
className="flex size-8 items-center justify-center rounded-lg text-fg-quaternary hover:text-fg-secondary hover:bg-primary_hover transition duration-100 ease-linear"
title="More actions"
>
<FontAwesomeIcon icon={faEllipsisVertical} className="size-4" />
</button>
{open && (
<div className="absolute top-full right-0 mt-1 w-40 rounded-xl bg-primary shadow-xl ring-1 ring-secondary z-50 overflow-hidden py-1">
<button
onClick={(e) => { e.stopPropagation(); window.open(`sms:+91${phone}`, '_self'); setOpen(false); }}
className="flex w-full items-center gap-2 px-3 py-2 text-xs text-primary hover:bg-primary_hover text-left"
>
<FontAwesomeIcon icon={faCommentDots} className="size-3.5 text-fg-quaternary" />
Send SMS
</button>
<button
onClick={(e) => { e.stopPropagation(); window.open(`https://wa.me/91${phone}`, '_blank'); setOpen(false); }}
className="flex w-full items-center gap-2 px-3 py-2 text-xs text-primary hover:bg-primary_hover text-left"
>
<FontAwesomeIcon icon={faMessageDots} className="size-3.5 text-fg-quaternary" />
WhatsApp
</button>
</div>
)}
</div>
);
};
export const PatientsPage = () => {
const { patients, loading } = useData();
@@ -178,7 +135,6 @@ export const PatientsPage = () => {
<Table.Head label="EMAIL" />
<Table.Head label="GENDER" />
<Table.Head label="AGE" />
<Table.Head label="" className="w-12" />
</Table.Header>
<Table.Body items={pagedPatients} dependencies={[selectedPatient?.id]}>
{(patient) => {
@@ -192,10 +148,10 @@ export const PatientsPage = () => {
: '?';
return (
<Table.Row
<Table.Row
id={patient.id}
className={cx(
'cursor-pointer',
'cursor-pointer group/row',
selectedPatient?.id === patient.id && 'bg-brand-primary'
)}
onAction={() => {
@@ -255,12 +211,6 @@ export const PatientsPage = () => {
</span>
</Table.Cell>
{/* Hamburger — SMS + WhatsApp */}
<Table.Cell>
{phone ? (
<HamburgerMenu phone={phone} />
) : null}
</Table.Cell>
</Table.Row>
);
}}