mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-05-18 20:08:19 +00:00
feat: appointments v2 + patients redesign + call history agent filter + datepicker placement
Appointments v2: - Lean 6-column table (eye icon, patient 2-line, date+time 2-line, doctor+dept 2-line, status badge, reminder button) - Detail side panel on eye click (read-only: all fields + patient phone via PhoneActionCell) - Reschedule flow: pencil in panel → modal confirm → dedicated ReschedulePanel with department/doctor/date/slot/complaint fields - Cancel flow: modal confirm before cancelling - WhatsApp reminder button for upcoming booked appointments - DatePicker popoverPlacement prop for narrow panels (opens upward) Patients page redesign: - Phone column uses PhoneActionCell (clickable to dial) - Email split into own column - Actions column replaced by hamburger menu (SMS + WhatsApp) - View (eye) button removed — row click opens profile panel Call History agent filter: - Missed calls excluded from agent's personal history - Chain name parsing for agent matching - "Missed" filter option hidden for agents - Subtitle: "134 completed" (no "0 missed") DatePicker: - New popoverPlacement prop forwarded to AriaPopover - Default "bottom start", use "top start" in constrained panels Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,17 +1,16 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
// useNavigate removed — row click opens profile panel
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faUser, faMagnifyingGlass, faEye, faCommentDots, faMessageDots, faSidebarFlip, faSidebar } from '@fortawesome/pro-duotone-svg-icons';
|
||||
import { faUser, faMagnifyingGlass, faCommentDots, faMessageDots, faEllipsisVertical, faSidebarFlip, faSidebar } from '@fortawesome/pro-duotone-svg-icons';
|
||||
import { faIcon } from '@/lib/icon-wrapper';
|
||||
|
||||
const SearchLg = faIcon(faMagnifyingGlass);
|
||||
import { Avatar } from '@/components/base/avatar/avatar';
|
||||
// Button removed — actions are icon-only now
|
||||
import { Input } from '@/components/base/input/input';
|
||||
import { Table, TableCard } from '@/components/application/table/table';
|
||||
import { PaginationPageDefault } from '@/components/application/pagination/pagination';
|
||||
|
||||
import { ClickToCallButton } from '@/components/call-desk/click-to-call-button';
|
||||
import { PhoneActionCell } from '@/components/call-desk/phone-action-cell';
|
||||
import { PatientProfilePanel } from '@/components/shared/patient-profile-panel';
|
||||
import { useData } from '@/providers/data-provider';
|
||||
import { getInitials } from '@/lib/format';
|
||||
@@ -55,9 +54,52 @@ 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();
|
||||
const navigate = useNavigate();
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [selectedPatient, setSelectedPatient] = useState<Patient | null>(null);
|
||||
const [panelOpen, setPanelOpen] = useState(false);
|
||||
@@ -132,10 +174,11 @@ export const PatientsPage = () => {
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Head label="PATIENT" isRowHeader />
|
||||
<Table.Head label="CONTACT" />
|
||||
<Table.Head label="PHONE" />
|
||||
<Table.Head label="EMAIL" />
|
||||
<Table.Head label="GENDER" />
|
||||
<Table.Head label="AGE" />
|
||||
<Table.Head label="ACTIONS" />
|
||||
<Table.Head label="" className="w-12" />
|
||||
</Table.Header>
|
||||
<Table.Body items={pagedPatients} dependencies={[selectedPatient?.id]}>
|
||||
{(patient) => {
|
||||
@@ -180,18 +223,22 @@ export const PatientsPage = () => {
|
||||
</div>
|
||||
</Table.Cell>
|
||||
|
||||
{/* Contact */}
|
||||
{/* Phone — clickable to dial */}
|
||||
<Table.Cell>
|
||||
<div className="flex flex-col">
|
||||
{phone ? (
|
||||
<span className="text-sm text-secondary">{phone}</span>
|
||||
) : (
|
||||
<span className="text-sm text-placeholder">No phone</span>
|
||||
)}
|
||||
{email ? (
|
||||
<span className="text-xs text-tertiary truncate max-w-[200px]">{email}</span>
|
||||
) : null}
|
||||
</div>
|
||||
{phone ? (
|
||||
<PhoneActionCell phoneNumber={phone} displayNumber={phone} />
|
||||
) : (
|
||||
<span className="text-sm text-placeholder">No phone</span>
|
||||
)}
|
||||
</Table.Cell>
|
||||
|
||||
{/* Email */}
|
||||
<Table.Cell>
|
||||
{email ? (
|
||||
<span className="text-sm text-tertiary truncate max-w-[200px] block">{email}</span>
|
||||
) : (
|
||||
<span className="text-sm text-quaternary">—</span>
|
||||
)}
|
||||
</Table.Cell>
|
||||
|
||||
{/* Gender */}
|
||||
@@ -208,40 +255,11 @@ export const PatientsPage = () => {
|
||||
</span>
|
||||
</Table.Cell>
|
||||
|
||||
{/* Actions */}
|
||||
{/* Hamburger — SMS + WhatsApp */}
|
||||
<Table.Cell>
|
||||
<div className="flex items-center gap-1">
|
||||
{phone && (
|
||||
<>
|
||||
<ClickToCallButton
|
||||
phoneNumber={phone}
|
||||
size="sm"
|
||||
label=""
|
||||
/>
|
||||
<button
|
||||
onClick={() => window.open(`sms:+91${phone}`, '_self')}
|
||||
title="SMS"
|
||||
className="flex size-8 items-center justify-center rounded-lg text-fg-quaternary hover:text-fg-brand-secondary hover:bg-primary_hover transition duration-100 ease-linear"
|
||||
>
|
||||
<FontAwesomeIcon icon={faCommentDots} className="size-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => window.open(`https://wa.me/91${phone}`, '_blank')}
|
||||
title="WhatsApp"
|
||||
className="flex size-8 items-center justify-center rounded-lg text-fg-quaternary hover:text-[#25D366] hover:bg-primary_hover transition duration-100 ease-linear"
|
||||
>
|
||||
<FontAwesomeIcon icon={faMessageDots} className="size-4" />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
onClick={() => navigate(`/patient/${patient.id}`)}
|
||||
title="View patient"
|
||||
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"
|
||||
>
|
||||
<FontAwesomeIcon icon={faEye} className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
{phone ? (
|
||||
<HamburgerMenu phone={phone} />
|
||||
) : null}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user