import React, { useState, useEffect, useMemo } from 'react'; import { Button } from '@carbon/react'; import { TwoFactorAuthentication, ChevronUp, ChevronDown } from '@carbon/react/icons'; import { useTranslation } from 'react-i18next'; import classNames from 'classnames'; import styles from '../card.scss'; import DependentsComponent from '../../dependants/dependants.component'; import { LocalResponse, type HIEBundleResponse, type EligibilityResponse } from '../../type'; import { hasDependents } from '../../helper'; import { launchWorkspace, PatientPhoto, showModal, navigate, closeWorkspace } from '@openmrs/esm-framework'; import { EnhancedPatientBannerPatientInfo } from '../../patient-banner/patient-banner.component'; import { findExistingLocalPatient, registerOrLaunchHIEPatient } from '../../search-bar/search-bar.resource'; import { launchOtpVerificationModal } from '../../../../shared/otp-verification'; import { otpManager, useOtpSource, cleanupAllOTPs } from './hie-card.resource'; import { useMultipleActiveVisits } from '../../dependants/dependants.resource'; import { sanitizePhoneNumber } from '../../../../shared/utils'; interface HIEDisplayCardProps { bundle: HIEBundleResponse; bundleIndex: number; searchedNationalId: string | null; otpExpiryMinutes?: number; localSearchResults?: LocalResponse | null; eligibilityResponse?: EligibilityResponse; isEligibilityLoading?: boolean; } const HIEDisplayCard: React.FC = ({ bundle, bundleIndex, searchedNationalId, otpExpiryMinutes = 5, localSearchResults = null, eligibilityResponse, isEligibilityLoading = false, }) => { const { t } = useTranslation(); const [showDependentsForPatient, setShowDependentsForPatient] = useState>(new Set()); const [verifiedPatients, setVerifiedPatients] = useState>(new Set()); const [otpRequestedFor, setOtpRequestedFor] = useState>(new Set()); const [localPatientCache, setLocalPatientCache] = useState>(new Map()); const [loadingLocalPatients, setLoadingLocalPatients] = useState>(new Set()); const { otpSource, isLoading: isLoadingOtpSource, error: otpSourceError } = useOtpSource(); useEffect(() => { if (otpSource) { otpManager.setOtpSource(otpSource); } }, [otpSource]); useEffect(() => { return () => { cleanupAllOTPs(); }; }, []); const patientUuids = useMemo(() => { return ( bundle.entry ?.map((entry) => { const localPatient = localPatientCache.get(entry.resource.id); return localPatient?.uuid || null; }) .filter(Boolean) || [] ); }, [bundle.entry, localPatientCache]); const visits = useMultipleActiveVisits(patientUuids); useEffect(() => { const searchForExistingPatients = async () => { if (!bundle.entry) { return; } const searchPromises = bundle.entry.map(async (entry) => { const patient = entry.resource; const patientUuid = patient.id; if (localPatientCache.has(patientUuid)) { return; } setLoadingLocalPatients((prev) => new Set(prev.add(patientUuid))); try { const existingPatient = await findExistingLocalPatient(patient, false); setLocalPatientCache((prev) => new Map(prev.set(patientUuid, existingPatient))); } catch (error) { setLocalPatientCache((prev) => new Map(prev.set(patientUuid, null))); } finally { setLoadingLocalPatients((prev) => { const newSet = new Set(prev); newSet.delete(patientUuid); return newSet; }); } }); await Promise.all(searchPromises); }; searchForExistingPatients(); }, [bundle.entry, localPatientCache]); const toggleDependentsVisibility = (patientId: string) => { setShowDependentsForPatient((prev) => { const newSet = new Set(prev); if (newSet.has(patientId)) { newSet.delete(patientId); } else { newSet.add(patientId); } return newSet; }); }; const getPatientPhoneNumber = (patient: any): string => { const phoneContact = patient.telecom?.find( (contact: any) => contact.system === 'phone' || contact.use === 'mobile', ); if (phoneContact?.value) { return sanitizePhoneNumber(phoneContact.value); } const localPatient = localPatientCache.get(patient.id); if (localPatient) { const phoneAttribute = localPatient.attributes?.find( (attr: any) => attr.attributeType?.display?.toLowerCase().includes('phone') || attr.attributeType?.display?.toLowerCase().includes('mobile') || attr.attributeType?.display?.toLowerCase().includes('telephone'), ); if (phoneAttribute?.value) { return sanitizePhoneNumber(phoneAttribute.value); } } return '254700000000'; }; const handleOTPRequest = (patientUuid: string) => { setOtpRequestedFor((prev) => new Set(prev).add(patientUuid)); }; const handleOTPVerificationSuccess = (patientId: string) => { setVerifiedPatients((prev) => new Set(prev).add(patientId)); setOtpRequestedFor((prev) => { const newSet = new Set(prev); newSet.delete(patientId); return newSet; }); }; const handleQueuePatient = (activeVisit: any, patientUuid: string) => { const dispose = showModal('transition-patient-to-latest-queue-modal', { closeModal: () => { dispose(); }, activeVisit, }); }; const createDynamicOTPHandlers = (patientUuid: string, patientName: string, phoneNumber: string) => { return { onRequestOtp: async (phone: string): Promise => { const sanitizedPhone = sanitizePhoneNumber(phone); try { if (!otpSource) { throw new Error('OTP source not configured. Please contact your administrator.'); } otpManager.setOtpSource(otpSource); await otpManager.requestOTP(sanitizedPhone, patientName, otpExpiryMinutes, searchedNationalId, patientUuid); } catch (error) { throw error; } }, onVerify: async (otp: string, _phoneNumber?: string): Promise => { const sanitizedPhone = sanitizePhoneNumber(phoneNumber); try { if (!otpSource) { throw new Error('OTP source not configured. Please contact your administrator.'); } otpManager.setOtpSource(otpSource); const isValid = await otpManager.verifyOTP(sanitizedPhone, otp, patientUuid); if (!isValid) { throw new Error('OTP verification failed'); } } catch (error) { throw error; } }, cleanup: (): void => { otpManager.cleanupExpiredOTPs(); }, }; }; if (!bundle.entry || bundle.entry.length === 0) { return null; } return ( <> {bundle.entry.map((entry, entryIndex) => { const patient = entry.resource; const patientName = patient.name?.[0]?.text || `${patient.name?.[0]?.given?.join(' ') || ''} ${patient.name?.[0]?.family || ''}`.trim() || 'Unknown Patient'; const patientUuid = patient.id; const patientKey = `${bundleIndex}-${entryIndex}`; const showDependents = showDependentsForPatient.has(patientKey); const patientHasDependents = hasDependents(patient); const isVerified = verifiedPatients.has(patientUuid); const otpRequested = otpRequestedFor.has(patientUuid); const localPatient = localPatientCache.get(patientUuid); const hasLocal = !!localPatient; const isSearchingLocal = loadingLocalPatients.has(patientUuid); const visitIndex = patientUuids.findIndex((uuid) => uuid === localPatient?.uuid); const activeVisit = visitIndex !== -1 ? visits[visitIndex]?.activeVisit : null; const hasActiveVisit = !!activeVisit; const patientPhoneNumber = getPatientPhoneNumber(patient); const { onRequestOtp, onVerify, cleanup } = createDynamicOTPHandlers( patientUuid, patientName, patientPhoneNumber, ); return (
{!isVerified && !otpRequested && ( )} {otpRequested && !isVerified && ( )} {isVerified && ( <> {hasLocal && !hasActiveVisit && ( )} {hasLocal && hasActiveVisit && ( )} {!hasLocal && ( )} {patientHasDependents && ( )} )}
{isVerified && showDependents && patientHasDependents && (
)}
); })} ); }; export default HIEDisplayCard;