• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

medplum / medplum / 30567424760

30 Jul 2026 05:35PM UTC coverage: 92.022% (+0.03%) from 91.994%
30567424760

push

github

web-flow
Provider: Add Message Settings to Messages (#9621)

* feat(react): rework New Message dialog with MultiResourceInput

Replace the QuestionnaireForm-based practitioner picker with
MultiResourceInput, defaulting the signed-in provider. Make the patient
required and disable Next until a patient and at least one practitioner are
selected. Apply the shared Send-Fax-style modal spacing/font.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Kevin Shaw <kevin@medplum.com>

* feat(react): add Message Settings dialog for threads

Add EditTopicDialog, opened from a new info-icon button in the thread header,
to edit a thread's topic and practitioner participants after creation. References
are resolved in parallel before render so all fields appear together, with a
failed patient read handled independently. Threads with no practitioner recipient
fall back to showing the sender practitioner. Non-practitioner recipients are
preserved on save.

Add useThreadInbox.updateThreadParent so an edited thread updates in place rather
than refetching, keeping a draft thread (no reply yet) in the list. Order the
inbox by latest message time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Kevin Shaw <kevin@medplum.com>

* feat(react): auto-select first thread in inbox

When no thread is selected and the list has items, navigate to the first
thread, matching the Tasks/Faxes boards. Add an optional replace flag to the
Medplum navigate function (forwarded by the app router wrappers) so the implicit
selection replaces history instead of adding a back-button entry that would
re-trigger the selection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Kevin Shaw <kevin@medplum.com>

* fix(react): type resolved practitioners via discriminant narrowing

The full tsc build (not run by vitest/esbuild) rejected the explicit
`PromiseFulfilledResult<Practitioner>` type predicate because readReference
resolves to WithId<Prac... (continued)

22738 of 25819 branches covered (88.07%)

Branch coverage included in aggregate %.

103 of 113 new or added lines in 10 files covered. (91.15%)

1 existing line in 1 file now uncovered.

40309 of 42694 relevant lines covered (94.41%)

12266.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.57
/packages/react/src/chat/ThreadInbox/NewTopicDialog.tsx
1
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
2
// SPDX-License-Identifier: Apache-2.0
3
import { Box, Button, Modal, Stack } from '@mantine/core';
4
import { showNotification } from '@mantine/notifications';
5
import { createReference, normalizeErrorString } from '@medplum/core';
6
import type { Communication, Patient, Practitioner, Reference } from '@medplum/fhirtypes';
7
import { useMedplum, useMedplumProfile } from '@medplum/react-hooks';
8
import type { JSX } from 'react';
9
import { useMemo, useState } from 'react';
10
import classes from './EditThreadDialog.module.css';
11
import { ThreadMessageForm } from './ThreadMessageForm';
12

13
/**
14
 * Props for the NewTopicDialog component.
15
 * @param subject - The patient to associate with the new thread. When provided and `allowPatientSelection` is false, the patient field is pre-filled and disabled.
16
 * @param opened - Whether the dialog is open.
17
 * @param onClose - Callback fired when the dialog is closed.
18
 * @param onSubmit - Callback fired with the created Communication resource after successful submission.
19
 * @param allowPatientSelection - When true, the patient field is an editable search input. When false (default), the field is pre-filled from `subject` and disabled. Use true for provider-facing contexts, false for patient-facing apps.
20
 */
21
export interface NewTopicDialogProps {
22
  subject: Reference<Patient> | Patient | undefined;
23
  opened: boolean;
24
  onClose: () => void;
25
  onSubmit?: (communication: Communication) => void;
26
  allowPatientSelection?: boolean;
27
}
28

29
export const NewTopicDialog = (props: NewTopicDialogProps): JSX.Element => {
3✔
30
  const { subject, opened, onClose, onSubmit, allowPatientSelection = false } = props;
32✔
31
  const medplum = useMedplum();
32✔
32
  const profile = useMedplumProfile();
32✔
33
  const profileRef = useMemo(() => (profile ? createReference(profile) : undefined), [profile]);
32!
34

35
  // Default the signed-in provider as the first practitioner recipient.
36
  const initialPractitioners = useMemo<Reference<Practitioner>[]>(
32✔
37
    () => (profile?.resourceType === 'Practitioner' ? [createReference(profile)] : []),
17!
38
    [profile]
39
  );
40

41
  const [topic, setTopic] = useState('');
32✔
42
  const [practitioners, setPractitioners] = useState<Reference<Practitioner>[]>(initialPractitioners);
32✔
43
  const [patient, setPatient] = useState<Reference<Patient> | undefined>(
32✔
44
    subject ? createReference(subject as Patient) : undefined
32✔
45
  );
46

47
  const handleSubmit = async (): Promise<void> => {
32✔
48
    // The Next button is disabled until both are set; this guard also narrows `patient`.
49
    if (!patient || practitioners.length === 0) {
3!
UNCOV
50
      return;
×
51
    }
52

53
    const communication: Communication = {
3✔
54
      resourceType: 'Communication',
55
      status: 'in-progress',
56
      subject: patient,
57
      sender: profileRef,
58
      recipient: [patient, ...practitioners],
59
      topic: topic ? { text: topic } : undefined,
3✔
60
    };
61

62
    try {
3✔
63
      const createdCommunication = await medplum.createResource(communication);
3✔
64

65
      onClose();
2✔
66
      onSubmit?.(createdCommunication);
2✔
67
    } catch (error) {
68
      showNotification({ color: 'red', message: normalizeErrorString(error) });
1✔
69
    }
70
  };
71

72
  return (
32✔
73
    <Modal opened={opened} onClose={onClose} title="New Message" size="md" classNames={classes}>
74
      <Stack gap={0}>
75
        <Stack gap="lg" p="lg">
76
          <ThreadMessageForm
77
            defaultPractitioners={initialPractitioners}
78
            onPractitionersChange={setPractitioners}
79
            topic={topic}
80
            onTopicChange={setTopic}
81
            defaultPatient={patient}
82
            onPatientChange={setPatient}
83
            allowPatientSelection={allowPatientSelection}
84
          />
85
        </Stack>
86

87
        <Box px="lg" pb="lg">
88
          <Button w="100%" onClick={handleSubmit} disabled={!patient || practitioners.length === 0}>
48✔
89
            Next
90
          </Button>
91
        </Box>
92
      </Stack>
93
    </Modal>
94
  );
95
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc