mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-05-18 20:08:19 +00:00
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>
125 lines
4.8 KiB
TypeScript
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>
|
|
);
|
|
};
|