import React, { useState, useMemo, useCallback, useEffect } 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 { LocalResponse, type HIEBundleResponse, type EligibilityResponse } from '../../type'; import { launchWorkspace, PatientPhoto, showModal, closeWorkspace, navigate } from '@openmrs/esm-framework'; import { EnhancedPatientBannerPatientInfo } from '../../patient-banner/patient-banner.component'; import { convertLocalPatientToFHIR, getNationalIdFromPatient, hasDependents } from '../../helper'; import { launchOtpVerificationModal } from '../../../../shared/otp-verification'; import DependentsComponent from '../../dependants/dependants.component'; import { useMultipleActiveVisits } from '../../dependants/dependants.resource'; import { otpManager, useOtpSource, cleanupAllOTPs } from '../HIE-card/hie-card.resource'; import { sanitizePhoneNumber } from '../../../../shared/utils'; interface LocalPatientCardProps { localSearchResults: LocalResponse; syncedPatients: Set; searchedNationalId: string; otpExpiryMinutes?: number; hieSearchResults?: Array | null; eligibilityResponse?: EligibilityResponse; isEligibilityLoading?: boolean; } const LocalPatientCard: React.FC = ({ localSearchResults, syncedPatients, searchedNationalId, otpExpiryMinutes = 5, hieSearchResults = null, eligibilityResponse, isEligibilityLoading = false, }) => { const { t } = useTranslation(); const [verifiedPatients, setVerifiedPatients] = useState>(new Set()); const [otpRequestedFor, setOtpRequestedFor] = useState>(new Set()); const [activePhoneNumbers, setActivePhoneNumbers] = useState>(new Map()); const [showDependentsForPatient, setShowDependentsForPatient] = useState>(new Set()); const { otpSource, isLoading: isLoadingOtpSource, error: otpSourceError } = useOtpSource(); useEffect(() => { if (otpSource) { otpManager.setOtpSource(otpSource); } }, [otpSource]); // Clean up OTPs when component unmounts useEffect(() => { return () => { cleanupAllOTPs(); }; }, []); const patientUuids = useMemo(() => { return localSearchResults?.map((patient) => patient.uuid) || []; }, [localSearchResults]); const visits = useMultipleActiveVisits(patientUuids); const findHIEPatientData = useCallback( (localPatient: any): any => { if (!hieSearchResults || !Array.isArray(hieSearchResults)) { return null; } const fhirPatient = convertLocalPatientToFHIR(localPatient); const localNationalId = getNationalIdFromPatient(fhirPatient); if (!localNationalId) { return null; } for (const bundle of hieSearchResults) { if (bundle.entry) { for (const entry of bundle.entry) { const hieNationalId = getNationalIdFromPatient(entry.resource); if (hieNationalId === localNationalId) { return entry.resource; } } } } return null; }, [hieSearchResults], ); const toggleDependentsVisibility = useCallback((patientUuid: string) => { setShowDependentsForPatient((prev) => { const newSet = new Set(prev); if (newSet.has(patientUuid)) { newSet.delete(patientUuid); } else { newSet.add(patientUuid); } return newSet; }); }, []); const getPatientPhoneNumber = useCallback((localPatient: any): string => { 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 = useCallback((patientUuid: string) => { setOtpRequestedFor((prev) => new Set(prev).add(patientUuid)); }, []); const handleOTPVerificationSuccess = useCallback((patientId: string) => { setVerifiedPatients((prev) => new Set(prev).add(patientId)); setOtpRequestedFor((prev) => { const newSet = new Set(prev); newSet.delete(patientId); return newSet; }); setActivePhoneNumbers((prev) => { const newMap = new Map(prev); newMap.delete(patientId); return newMap; }); }, []); const createDynamicOTPHandlers = useCallback( (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(); }, }; }, [otpExpiryMinutes, searchedNationalId, otpSource], ); const handleQueuePatient = useCallback((activeVisit: any, patientUuid: string) => { const dispose = showModal('transition-patient-to-latest-queue-modal', { closeModal: () => { dispose(); }, activeVisit, }); }, []); if (!localSearchResults || localSearchResults.length === 0) { return null; } return ( <> {localSearchResults.map((localPatient, index) => { const fhirPatient = convertLocalPatientToFHIR(localPatient); const patientName = localPatient?.person?.personName?.display || `${localPatient?.person?.personName?.givenName || ''} ${localPatient?.person?.personName?.middleName || ''} ${ localPatient?.person?.personName?.familyName || '' }`.trim() || 'Unknown Patient'; const patientUuid = localPatient.uuid; const patientKey = `local-${index}`; const isVerified = verifiedPatients.has(patientUuid); const otpRequested = otpRequestedFor.has(patientUuid); const activeVisit = visits[index]?.activeVisit || null; const hasActiveVisit = !!activeVisit; const hiePatientData: any = findHIEPatientData(localPatient); const patientHasDependents: boolean = hiePatientData ? hasDependents(hiePatientData) : false; const showDependents: boolean = showDependentsForPatient.has(patientUuid); const patientPhoneNumber = getPatientPhoneNumber(localPatient); const { onRequestOtp, onVerify, cleanup } = createDynamicOTPHandlers( patientUuid, patientName, patientPhoneNumber, ); return (
{!isVerified && !otpRequested && ( )} {otpRequested && !isVerified && ( )} {isVerified && ( <> {!hasActiveVisit && ( )} {hasActiveVisit && ( )} {patientHasDependents && hiePatientData && ( )} )}
{isVerified && showDependents && hiePatientData && (
)}
); })} ); }; export default LocalPatientCard;