import { useLayoutEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Communication, Practitioner, Reference } from 'fhir/r4'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../ui/dialog'; import { useAuthServiceContext, useCommunicationServiceContext, useFhirServiceContext, } from '../../services'; import { useCommunicationManager } from '../../hooks'; import { formatFhirHumanName } from '../../utils/fhirHumanName'; import PresenceDot from '../PresenceDot/PresenceDot'; import { namedSend } from '../../utils/messaging/sendOperation'; import { MAY_HAVE_BEEN_SENT } from '../../hooks/useHeldSendOperations'; import { useScopedSendRecovery } from '../../hooks/useScopedSendRecovery'; import EarlierSendCheck from '../messages/EarlierSendCheck'; export type QuickMessageModalProps = { open: boolean; target?: Practitioner; onClose: () => void; }; // In-overview quick message to a staff member. Keeps the receptionist on the overview: // send here, or follow "View full conversation" to the full staff-messages page. const QuickMessageModal = ({ open, target, onClose }: QuickMessageModalProps) => { const navigate = useNavigate(); const { currentUser } = useAuthServiceContext(); const { createCommunication, createCommunicationForThread } = useCommunicationServiceContext(); const { createReferenceObject } = useFhirServiceContext(); const { openStaffDirectThread } = useCommunicationManager(); const [message, setMessage] = useState(''); const [sending, setSending] = useState(false); const [error, setError] = useState(); const recovery = useScopedSendRecovery('quick-message', target?.id ? `staff:${target.id}` : undefined); // Bumped on every keystroke: an answer clears only the draft it was sent from. const revision = useRef(0); // Every open and close starts a new lifetime: a send pressed in an earlier one neither shows nor closes anything. const lifetime = useRef(0); useLayoutEffect(() => { lifetime.current += 1; setSending(false); setError(undefined); return recovery.reopen(); }, [open, target?.id, recovery.reopen]); const targetName = formatFhirHumanName(target?.name) || 'staff member'; const handleSend = async () => { if (!message.trim() || !target?.id || !currentUser?.fhir || recovery.waits) { return; } const sentIn = lifetime.current; const attempt = recovery.begin(undefined, () => lifetime.current === sentIn); const words = message; const drafted = revision.current; const sender = currentUser.fhir as Practitioner; setSending(true); setError(undefined); const result = await recovery.run(attempt, `staff:${target.id}|${words}`, async (send, leaving) => { const thread = await openStaffDirectThread(sender, target); attempt.admit(); const recipientReferences: Reference[] = [ createReferenceObject( target.resourceType, target.id as string, formatFhirHumanName(target.name), ), ]; const communication: Communication = createCommunicationForThread( words, thread.request.id as string, sender, recipientReferences, ); leaving(); return createCommunication(namedSend(communication, send), send); }); if (lifetime.current !== sentIn || result.state === 'retired') { return; } setSending(false); if (result.state === 'sent') { if (revision.current === drafted) { setMessage(''); onClose(); } return; } if (result.state !== 'not-sent') { console.error('Quick message send failed:', result.error); } setError(result.state === 'not-sent' ? result.reason : result.unsure ? 'Message not confirmed. ' + MAY_HAVE_BEEN_SENT : 'Could not send the message. Check it before sending again.'); }; const viewFullConversation = () => { if (!target?.id) { return; } onClose(); navigate('/dashboard/staff-messages', { state: { tab: 'colleagues', openWith: target.id } }); }; return ( { if (!next) { onClose(); } }}> {target?.id && ( )} Message {targetName}