import { useEffect, useState } from 'react'; import type { VisitInvitationView } from '../../types/visitInvitation'; import { VisitAttendeeSummary } from './VisitAttendeeSummary'; import { DECLINE_REASON_MAX, VISIT_ACCEPT_CLOSED, VISIT_TIME_PASSED, acceptIsUnfinished, overSentence, visitIsOver, visitTimePhase, visitTitle, visitWhen, } from '../../utils/visitInvitationWords'; import { useVisitClock } from '../../hooks/useVisitClock'; /** * The invitee's own answer to a team video visit: which team and patient, when, whether the time moved, whether their * time is held, and Accept / Decline with an optional reason. Used by the invitee's view and by a staff member who is * themselves invited, so both say the same thing. */ export const YourVisitAnswer = ({ view, busy, onRespond, }: { view: Pick; busy: boolean; onRespond: (response: 'accepted' | 'declined', declineReason?: string) => void; }) => { const [declining, setDeclining] = useState(false); const [reason, setReason] = useState(''); const answered = view.yourInvitation?.response; const now = useVisitClock([view]); const phase = visitTimePhase(view, now); // A visit that is over takes no answer; what was answered before stays readable. Over by its status, or by its // scheduled end having passed: either way it is history, and a booked one is still booked. const overByStatus = visitIsOver(view.appointmentStatus); const over = overByStatus || phase === 'ended'; // From its start the server takes no accept; a decline, which only gives time back, is still taken until its end. const begun = !over && phase === 'begun'; // A moved time needs a fresh answer, whatever was answered before. const needsAnswer = !over && (!answered || view.timeChangedSinceAnswer); // An accept whose hold is not confirmed (including no reservation reported) keeps its buttons, so it can be finished. const unfinished = !over && acceptIsUnfinished({ response: answered, reservation: view.reservation }); const when = visitWhen(view); // A decline being written when the visit ends is retired with its reason: there is nothing left to send. useEffect(() => { if (!over) return; setDeclining(false); setReason(''); }, [over]); return (

{visitTitle(view)}

{when &&

{when}

} {view.teamUnresolved && (

This invitation was sent before visits named their team.

)} {/* Who hosts it and who else is invited, as the server shows an invitee: read-only, beside their own answer. */} {overByStatus &&
{overSentence(view.appointmentStatus)}
} {!overByStatus && over &&
{VISIT_TIME_PASSED}
} {begun && (needsAnswer || unfinished) && (
{VISIT_ACCEPT_CLOSED}
)} {!over && !begun && view.timeChangedSinceAnswer && (
The time of this visit has changed since you answered. Please answer again.
)} {/* On a visit that is over, an earlier accept is history: no hold claim and nothing to finish. */} {over && answered === 'accepted' && (

You accepted this invitation.

)} {/* Once it has begun, an accept that did not finish cannot be finished: say so, never "accept again". */} {begun && !needsAnswer && answered === 'accepted' && view.reservation?.state !== 'reserved' && view.reservation?.state !== 'not-applicable' && (

You accepted, but your time was not held.

)} {!over && !needsAnswer && answered === 'accepted' && !(begun && unfinished) && (

{view.reservation?.state === 'reserved' ? 'You accepted. The time is held on your calendar.' : view.reservation?.state === 'not-applicable' ? 'You accepted.' : view.reservation ? 'You accepted, but your time is not held yet. Accept again to finish.' : 'You accepted, but your time is not confirmed as held. Accept again to finish.'}

)} {!needsAnswer && answered === 'declined' && (

You declined this invitation.{view.declineReason ? ` Your reason: “${view.declineReason}”` : ''}

)} {(needsAnswer || unfinished) && !declining && ( <> {!begun &&

Accepting adds this visit to your calendar and holds the time.

} {!begun && ( )} )} {declining && !over && (