import { scannedPhotoSrc } from "../../utils/fhirPhoto"; import React, { useState } from "react"; import { RelatedPerson } from "fhir/r4"; import { SquarePen, UserRound } from "lucide-react"; import Contacts from "./Contacts.tsx"; import { useRelatedPersonServiceContext } from "../../services/RelatedPersonService.tsx"; import { useRelatedPersonIdentity } from "../../hooks/useRelatedPersonIdentity.tsx"; import { toast } from "sonner"; import { Button } from "../ui/button.tsx"; import { formatFhirHumanName } from "../../utils/fhirHumanName.tsx"; interface FhirRelatedPersonDisplayProps { relatedPerson?: RelatedPerson; selectedRelatedPerson?: RelatedPerson; setShowDisplaySheet?: (value: boolean) => void; handleEditClick?: ( index: React.MouseEvent, value: RelatedPerson, ) => void; isOpen?: boolean; onClose?: () => void; } export const FhirRelatedPersonDisplay: React.FC< FhirRelatedPersonDisplayProps > = ({ relatedPerson, setShowDisplaySheet, selectedRelatedPerson, handleEditClick, isOpen = true, }) => { const { updateRelatedPerson } = useRelatedPersonServiceContext(); const [submitting, setSubmitting] = useState(false); const [currentRelatedPerson, setCurrentRelatedPerson] = useState(relatedPerson); // RelatedPerson is now a thin link (Slice A, Phase 1) - no name/gender/birthDate/address on the // resource itself. Name + contact are resolved from the scoped server-side identity projection; // relationship stays sourced from the RP resource (still on the thin link). const { name: projectedName, contact: projectedContact } = useRelatedPersonIdentity( currentRelatedPerson?.id, ); const handleUpdateRelatedPerson = async ( updatedRelatedPerson: RelatedPerson, ) => { setSubmitting(true); try { const result = await updateRelatedPerson(updatedRelatedPerson); setCurrentRelatedPerson(result); return result; } catch (error: unknown) { console.error("Failed to update related person:", error); // Type-safe error message extraction let errorMessage = "An unknown error occurred"; if (error instanceof Error) { errorMessage = error.message; } else if (typeof error === "string") { errorMessage = error; } else if (error && typeof error === "object" && "message" in error) { errorMessage = String(error.message); } toast.error(`Failed to update contact: ${errorMessage}`, { position: "top-right", }); } finally { setSubmitting(false); } }; const renderContent = () => { const photo = currentRelatedPerson?.photo?.[0]; const photoSrc = scannedPhotoSrc(photo) ?? ""; const relationshipLabel = currentRelatedPerson?.relationship?.[0]?.text ? currentRelatedPerson.relationship[0].text.charAt(0).toUpperCase() + currentRelatedPerson.relationship[0].text.slice(1) : ""; // Name comes from the scoped identity projection now (the RelatedPerson resource itself is a // thin link with no name on it). Graceful fallback chain when the projection is empty/failed // (e.g. a legacy RelatedPerson with no home-patient link): relationship label, then a plain // "Name unavailable" - never blank or NaN. const displayName = formatFhirHumanName(projectedName, { emptyText: "" }) || relationshipLabel || "Name unavailable"; return ( <>
{photoSrc ? ( Profile ) : ( )}

{displayName} {currentRelatedPerson?.active === true && ( Active )}

{relationshipLabel}

{currentRelatedPerson?.communication?.map((comm, index) => ( {comm.language?.text || comm.language?.coding?.[0]?.display || comm.language?.coding?.[0]?.code || "Unknown"} ))}
{setShowDisplaySheet && handleEditClick && selectedRelatedPerson && ( )}
); }; if (!isOpen || !currentRelatedPerson) return null; return
{renderContent()}
; }; export default FhirRelatedPersonDisplay;