import React from 'react'; import { Address } from 'fhir/r4'; import { Text } from '../components/ui/text'; interface FHIRAddressDisplayProps { address?: Address[] | undefined; use?: string; separator?: string; emptyText?: string; className?: string; } export const formatSingleAddress = (address: Address): string => { if (!address) { return ''; } const parts = [ address.line?.join(', '), address.city, // US style: no comma between the state and the ZIP. [address.state, address.postalCode].filter(part => part).join(' ') ].filter(part => part); return parts.join(', ') || ''; }; /** * The address with the padding taken off every text part, and its `text` rebuilt from what is left. * * WHAT REACHES THE WIRE IS TRIMMED. " Main St " and "Main St" are the same address, and storing the * padded one makes them compare as two - which is what the location form's duplicate-address check was * seeing. A whitespace-ONLY value is refused at the field; this is about the padding on a real one. * * A BLANK LINE IS DROPPED, because an address line of spaces is not a line. Nothing else is: a city of * spaces has already been refused by the form, and silently deleting one here would hide that from * anything that did not ask. * * ONLY THE KEYS THAT WERE PRESENT come back, so this can never add `city: ""` to an address that simply * had no city - an empty string is a value and would be stored as one. */ export const trimFhirAddress = >(address: T): T & { text: string } => { const trim = (value: unknown) => (typeof value === 'string' ? value.trim() : value); const line = Array.isArray(address.line) ? address.line.map(trim).filter((part) => part !== '') : address.line; const trimmed = { ...address, ...(address.line !== undefined && { line }), ...(address.city !== undefined && { city: trim(address.city) }), ...(address.state !== undefined && { state: trim(address.state) }), ...(address.postalCode !== undefined && { postalCode: trim(address.postalCode) }), ...(address.country !== undefined && { country: trim(address.country) }), }; // Rebuilt from the TRIMMED parts. Composing it from the raw ones leaves the padding in the one field // most lists and pickers actually render. const text = [ ...(Array.isArray(trimmed.line) ? trimmed.line : []), trimmed.city, trimmed.state, trimmed.postalCode, trimmed.country, ].filter((part) => part).join(', '); return { ...trimmed, text } as unknown as T & { text: string }; }; export const normalizeFhirAddress = (address?: Address | null): string => { if (!address) { return ''; } return [ ...(address.line ?? []), address.city, address.state, address.postalCode, address.country ] .filter(Boolean) .join('|') .toLowerCase() .replace(/[^a-z0-9|]/g, ''); }; export const formatFhirAddress = ( fhirAddress?: Address[] | undefined, options: { use?: string; separator?: string; emptyText?: string; } = {} ): string => { const { use, separator = ', ', emptyText = '-' } = options; if (!fhirAddress?.length) { return emptyText; } const filteredAddresses = use ? fhirAddress.filter(addr => addr?.use === use) : fhirAddress; if (filteredAddresses.length === 0) { return emptyText; } return filteredAddresses .map(addr => formatSingleAddress(addr)) .filter(addr => addr) .join(separator); }; export const useFhirAddress = ( fhirAddress?: Address[] | undefined, options: { use?: string; separator?: string; emptyText?: string; } = {} ): string => { return formatFhirAddress(fhirAddress, options); }; export const getAddressByUse = (fhirAddress?: Address[] | undefined, use?: string): Address | undefined => { if (!fhirAddress?.length) { return undefined; } const addressWithUse = fhirAddress.find(addr => addr.use === use); return addressWithUse || fhirAddress[0]; }; export const formatFhirAddressByUse = (fhirAddress?: Address[] | undefined, use?: string): string => { const address = getAddressByUse(fhirAddress, use); if (!address) { return ''; } return formatSingleAddress(address); }; export const getHomeAddress = (fhirAddress?: Address[] | undefined): Address | undefined => { if (!fhirAddress?.length) { return undefined; } const homeAddress = fhirAddress.find(addr => addr?.use === 'home'); return homeAddress || fhirAddress[0]; }; export const getWorkAddress = (fhirAddress?: Address[] | undefined): Address | undefined => { if (!fhirAddress?.length) { return undefined; } return fhirAddress.find(addr => addr?.use === 'work'); }; const getAddressDisplayName = (address: Address): string => { if (address.use === 'home') { return 'Home'; } if (address.use === 'work') { return 'Work'; } if (address.use === 'temp') { return 'Temporary'; } if (address.use === 'old') { return 'Old'; } if (address.use === 'billing') { return 'Billing'; } if (address.type) { return address.type.charAt(0).toUpperCase() + address.type.slice(1); } return 'Address'; }; export const FHIRAddressDisplay: React.FC = ({ address, use, separator = ', ', emptyText = '-', className }) => { if (!address || address.length === 0) { return {emptyText}; } const filteredAddresses = use ? address.filter(addr => addr?.use === use) : address; if (filteredAddresses.length === 0) { return {emptyText}; } if (filteredAddresses.length > 1 && !use) { return (
{filteredAddresses.map((singleAddress, index) => { const displayName = getAddressDisplayName(singleAddress); const formattedAddress = formatSingleAddress(singleAddress); if (!formattedAddress) return null; return (
{displayName}: {formattedAddress}
); })}
); } const formattedAddress = formatFhirAddress(address, { use, separator, emptyText }); return ( {formattedAddress} ); };