Files
helix-engage/src/components/call-desk/phone-action-cell.tsx
saridsa2 bdabcb2ea4
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
feat: consistent UI across all list pages — PhoneActionCell, custom pills, eye icon
- 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>
2026-04-16 23:05:32 +05:30

125 lines
4.8 KiB
TypeScript

import { useState, useRef, useEffect } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPhone, faCommentDots, faEllipsisVertical, faMessageDots } from '@fortawesome/pro-duotone-svg-icons';
import { useSip } from '@/providers/sip-provider';
import { notify } from '@/lib/toast';
import { cx } from '@/utils/cx';
type PhoneActionCellProps = {
phoneNumber: string;
displayNumber: string;
leadId?: string;
onDial?: () => void;
};
export const PhoneActionCell = ({ phoneNumber, displayNumber, leadId: _leadId, onDial }: PhoneActionCellProps) => {
const { isRegistered, isInCall, dialOutbound } = useSip();
const [menuOpen, setMenuOpen] = useState(false);
const [dialing, setDialing] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);
const touchTimer = useRef<number | null>(null);
// Close menu on click outside
useEffect(() => {
if (!menuOpen) return;
const handleClick = (e: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
setMenuOpen(false);
}
};
document.addEventListener('mousedown', handleClick);
return () => document.removeEventListener('mousedown', handleClick);
}, [menuOpen]);
const handleCall = async () => {
if (!isRegistered || isInCall || dialing) return;
setMenuOpen(false);
setDialing(true);
try {
onDial?.();
await dialOutbound(phoneNumber);
} catch {
notify.error('Dial Failed', 'Could not place the call');
} finally {
setDialing(false);
}
};
const handleSms = () => {
setMenuOpen(false);
window.open(`sms:+91${phoneNumber}`, '_self');
};
const handleWhatsApp = () => {
setMenuOpen(false);
window.open(`https://wa.me/91${phoneNumber}`, '_blank');
};
// Long-press for mobile
const onTouchStart = () => {
touchTimer.current = window.setTimeout(() => setMenuOpen(true), 500);
};
const onTouchEnd = () => {
if (touchTimer.current) {
clearTimeout(touchTimer.current);
touchTimer.current = null;
}
};
const canCall = isRegistered && !isInCall && !dialing;
return (
<div className="relative flex items-center gap-1" ref={menuRef}>
{/* Clickable phone number — calls directly */}
<button
type="button"
onClick={canCall ? handleCall : undefined}
onTouchStart={onTouchStart}
onTouchEnd={onTouchEnd}
onContextMenu={(e) => { e.preventDefault(); setMenuOpen(true); }}
className={cx(
'flex items-center gap-1.5 rounded-md px-1.5 py-1 text-sm text-brand-secondary transition duration-100 ease-linear',
canCall
? 'cursor-pointer hover:bg-brand-primary'
: 'cursor-default',
)}
>
<FontAwesomeIcon icon={faPhone} className="size-3" />
<span className="whitespace-nowrap">{displayNumber}</span>
</button>
{/* Kebab menu trigger — SMS + WhatsApp */}
<button
type="button"
onClick={(e) => { e.stopPropagation(); setMenuOpen(!menuOpen); }}
className="flex size-6 items-center justify-center rounded-md text-fg-quaternary hover:text-fg-secondary hover:bg-primary_hover transition duration-100 ease-linear"
>
<FontAwesomeIcon icon={faEllipsisVertical} className="size-3" />
</button>
{/* Context menu — SMS + WhatsApp only (dial is the primary click) */}
{menuOpen && (
<div className="absolute left-0 top-full z-50 mt-1 w-40 rounded-lg bg-primary shadow-lg ring-1 ring-secondary py-1">
<button
type="button"
onClick={handleSms}
className="flex w-full items-center gap-2 px-3 py-2 text-sm text-secondary hover:bg-primary_hover"
>
<FontAwesomeIcon icon={faCommentDots} className="size-3.5 text-fg-brand-secondary" />
SMS
</button>
<button
type="button"
onClick={handleWhatsApp}
className="flex w-full items-center gap-2 px-3 py-2 text-sm text-secondary hover:bg-primary_hover"
>
<FontAwesomeIcon icon={faMessageDots} className="size-3.5 text-[#25D366]" />
WhatsApp
</button>
</div>
)}
</div>
);
};