import { useEffect, useRef, useState } from "react"; import { ShieldCheck } from "lucide-react"; import { Card } from "../../../components/ui/card"; import { Button } from "../../../components/ui/button"; import type { CollaborationTeam, ReferralAuthorizationRequest, TeamMembership } from "../../../services"; import { buildReferralAuthorization, decisionKeyOf } from "../../../services"; import { isSaveRetired, isSaveTimedOut, TIMED_OUT_MAY_HAVE_SAVED, useSaveScope } from "../../../utils/saveOperation"; import type { SaveTransport } from "../../../utils/saveOperation"; import { useRowOwnedSaves } from "../../../utils/rowOwnedSave"; import { describeServerRefusal } from "../../../utils/serverRefusalMessage"; import { MemberIdentity, usePractitionerIdentity } from "./MemberIdentity"; import { MemberAffiliation } from "./MemberAffiliation"; import { ReferralAuthorizationDialog } from "./ReferralAuthorizationDialog"; type TeamWithMembers = CollaborationTeam & { members: TeamMembership[] }; // Mounted only while a member is being decided, so the name lookup runs for that one person. const GiveAccessDialog = ({ member, busy, onAgree, onCancel }: { member: TeamMembership; busy: boolean; onAgree: () => void; onCancel: () => void; }) => { const identity = usePractitionerIdentity(member.displayName ? undefined : member.memberRef.split("/")[1], member.displayName); return ( } subjectIdentified={identity.status !== "unidentified"} busy={busy} agreeLabel="Agree and give access" onAgree={onAgree} onCancel={onCancel} /> ); }; interface GiveRecordAccessSectionProps { team: TeamWithMembers; authorizeMember: ( teamId: string, member: Pick, authorization: ReferralAuthorizationRequest, transport?: SaveTransport, ) => Promise; onChanged: () => Promise; /** False while the list behind this section is being re-read or could not be: no decision is offered on it. */ decisionsOpen?: boolean; } // Clinicians who joined the team but cannot read the record, because their invitation recorded no // permission and the patient's care does not cover them. The server flags them; giving access is the // same agreement an invitation asks for, sent for the row the patient is looking at. export const GiveRecordAccessSection = ({ team, authorizeMember, onChanged, decisionsOpen = true }: GiveRecordAccessSectionProps) => { const waiting = team.status === "active" ? team.members.filter((member) => member.state === "active" && member.awaitingPatientAuthorization === true) : []; const keyOf = (member: TeamMembership) => decisionKeyOf(team.teamId, member); const liveKeys = new Set(waiting.map(keyOf)); const [busyKey, setBusyKey] = useState(null); const [error, setError] = useState<{ message: string; reread: boolean } | null>(null); const [sent, setSent] = useState>(() => new Set()); const [authorizingKey, setAuthorizingKey] = useState(null); const authorizing = authorizingKey ? waiting.find((member) => keyOf(member) === authorizingKey) ?? null : null; const scope = useSaveScope(`${team.patientId}|${team.teamId}|access`, () => { setAuthorizingKey(null); setBusyKey(null); }); const rowSaves = useRowOwnedSaves(scope, liveKeys, decisionsOpen, (key) => { setBusyKey((current) => (current === key ? null : current)); }); const liveKeysRef = useRef(liveKeys); liveKeysRef.current = liveKeys; const liveKeyList = [...liveKeys].join("\n"); useEffect(() => { if (authorizingKey && !authorizing) setAuthorizingKey(null); }, [authorizingKey, authorizing]); useEffect(() => { if (!decisionsOpen) setAuthorizingKey(null); }, [decisionsOpen]); useEffect(() => { setSent((prev) => { const kept = [...prev].filter((key) => liveKeysRef.current.has(key)); return kept.length === prev.size ? prev : new Set(kept); }); }, [liveKeyList]); if (waiting.length === 0) return null; const giveAccess = async (member: TeamMembership) => { const key = keyOf(member); const operation = rowSaves.start(key); if (!operation) return; setBusyKey(key); setError(null); try { await operation.step(() => authorizeMember(team.teamId, member, buildReferralAuthorization(), operation.transport), true); } catch (failure) { if (!operation.belongs()) return; setBusyKey(null); setAuthorizingKey(null); if (isSaveRetired(failure)) return; const code = (failure as { body?: { code?: string } } | null)?.body?.code; const changed = isSaveTimedOut(failure) || code === "MEMBERSHIP_CHANGED" || code === "NOTHING_TO_AUTHORIZE" || code === "ALREADY_COVERED" || code === "TEAM_NOT_ACTIVE"; setError({ message: isSaveTimedOut(failure) ? TIMED_OUT_MAY_HAVE_SAVED : describeServerRefusal(failure, "Could not give this clinician access"), reread: changed, }); return; } if (!operation.belongs()) return; setSent((prev) => new Set(prev).add(key)); setAuthorizingKey(null); setBusyKey(null); await onChanged(); }; return (

Can't see your record yet

{error && (

{error.message}

{error.reread && ( )}
)}
{waiting.map((member) => (

On your team, but they can't see your record until you give them access.

{!decisionsOpen || sent.has(keyOf(member)) ? (

{decisionsOpen ? "Sent. Updating the list." : "Checking this list for changes."}

) : ( )}
))}
{authorizing && decisionsOpen && ( void giveAccess(authorizing)} onCancel={() => setAuthorizingKey(null)} /> )}
); }; export default GiveRecordAccessSection;