Skip to main content
Glama
useThreadInbox.ts6.41 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { useState, useEffect } from 'react'; import type { Communication } from '@medplum/fhirtypes'; import { useMedplum } from '@medplum/react'; import { getReferenceString } from '@medplum/core'; export interface UseThreadInboxOptions { query: string; threadId: string | undefined; offset?: number; count?: number; } export interface UseThreadInboxReturn { loading: boolean; error: Error | null; // Tuple: [Parent Thread, Last Message in Thread (optional)] threadMessages: [Communication, Communication | undefined][]; selectedThread: Communication | undefined; total: number | undefined; addThreadMessage: (message: Communication) => void; handleThreadStatusChange: (newStatus: Communication['status']) => Promise<void>; } /* useThreadInbox is a hook that fetches all communications and returns the thread messages and selected thread. All comunications returned do not have a partOf field. It also provides a function to update the status of the selected thread. @param query - The query to fetch all communications. @param threadId - The id of the thread to select. @returns The thread messages and selected thread. @returns A function to update the status of the selected thread. */ export function useThreadInbox({ query, threadId, offset, count }: UseThreadInboxOptions): UseThreadInboxReturn { const medplum = useMedplum(); const [loading, setLoading] = useState(false); const [threadMessages, setThreadMessages] = useState<[Communication, Communication | undefined][]>([]); const [selectedThread, setSelectedThread] = useState<Communication | undefined>(undefined); const [error, setError] = useState<Error | null>(null); const [total, setTotal] = useState<number | undefined>(undefined); useEffect(() => { const fetchAllCommunications = async (): Promise<void> => { const searchParams = new URLSearchParams(query); searchParams.append('identifier:not', 'ai-message-topic'); searchParams.append('part-of:missing', 'true'); if (offset !== undefined) { searchParams.append('_offset', offset.toString()); } if (count !== undefined) { searchParams.append('_count', count.toString()); } searchParams.append('_total', 'accurate'); const bundle = await medplum.search('Communication', searchParams.toString(), { cache: 'no-cache' }); const parents = bundle.entry ?.map((entry) => entry.resource as Communication) .filter((r): r is Communication => r !== undefined) || []; if (bundle.total !== undefined) { setTotal(bundle.total); } if (parents.length === 0) { setThreadMessages([]); return; } const queryParts = parents.map((parent) => { const safeId = parent.id?.replace(/-/g, '') || ''; const alias = `thread_${safeId}`; const ref = getReferenceString(parent); return ` ${alias}: CommunicationList( part_of: "${ref}" _sort: "-sent" _count: 1 ) { id meta { lastUpdated } partOf { reference } sender { display reference } payload { contentString } sent status } `; }); const fullQuery = ` query { ${queryParts.join('\n')} } `; const response = await medplum.graphql(fullQuery); const threadsWithReplies = parents .map((parent) => { const safeId = parent.id?.replace(/-/g, '') || ''; const alias = `thread_${safeId}`; const childList = response.data[alias] as Communication[] | undefined; const lastMessage = childList && childList.length > 0 ? childList[0] : undefined; return [parent, lastMessage]; }) .filter((thread): thread is [Communication, Communication] => thread[1] !== undefined); setThreadMessages(threadsWithReplies); }; setLoading(true); fetchAllCommunications() .catch((err) => { console.error('Error fetching inbox threads:', err); setError(err as Error); }) .finally(() => { setLoading(false); }); }, [medplum, query, offset, count]); useEffect(() => { const fetchThread = async (): Promise<void> => { if (threadId) { const thread = threadMessages.find((t) => t[0].id === threadId); if (thread) { setSelectedThread(thread[0]); } else { try { const communication: Communication = await medplum.readResource('Communication', threadId); if (communication.partOf === undefined) { setSelectedThread(communication); } else { const parentRef = communication.partOf[0].reference; if (parentRef) { const parent = await medplum.readReference({ reference: parentRef } as any); setSelectedThread(parent as Communication); } } } catch (err) { setError(err as Error); } } } else { setSelectedThread(undefined); } }; fetchThread().catch((err) => { setError(err as Error); }); }, [threadId, threadMessages, medplum]); const handleThreadStatusChange = async (newStatus: Communication['status']): Promise<void> => { if (!selectedThread) { return; } try { const updatedThread = await medplum.updateResource({ ...selectedThread, status: newStatus, }); setSelectedThread(updatedThread); setThreadMessages((prev) => prev.map(([parent, lastMsg]) => (parent.id === updatedThread.id ? [updatedThread, lastMsg] : [parent, lastMsg])) ); } catch (err) { setError(err as Error); } }; const addThreadMessage = (message: Communication): void => { // Assuming this adds a new TOP level thread setThreadMessages((prev) => [[message, undefined], ...prev]); }; return { loading, error, threadMessages, selectedThread, total, addThreadMessage, handleThreadStatusChange, }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server