Skip to main content
Glama
CreateAppointment.tsx6.49 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { Button, Group, Modal } from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; import { showNotification } from '@mantine/notifications'; import { createReference, getQuestionnaireAnswers, normalizeErrorString } from '@medplum/core'; import type { Appointment, Coding, Patient, Practitioner, Questionnaire, QuestionnaireResponse, Reference, Slot, } from '@medplum/fhirtypes'; import { QuestionnaireForm, useMedplum, useMedplumProfile } from '@medplum/react'; import { IconCircleCheck, IconCircleOff, IconEdit, IconTrash } from '@tabler/icons-react'; import type { JSX } from 'react'; import type { Event } from 'react-big-calendar'; import { useNavigate } from 'react-router'; import { CreateUpdateSlot } from './CreateUpdateSlot'; interface CreateAppointmentProps { patient?: Patient; event: Event | undefined; readonly opened: boolean; readonly handlers: { readonly open: () => void; readonly close: () => void; readonly toggle: () => void; }; readonly onAppointmentsUpdated: () => void; } /** * CreateAppointment component that allows the user to create an appointment from a slot. * @param props - CreateAppointmentProps * @returns A React component that displays the modal. */ export function CreateAppointment(props: CreateAppointmentProps): JSX.Element | null { const { patient, event, opened, handlers, onAppointmentsUpdated } = props; const slot: Slot | undefined = event?.resource; const [updateSlotOpened, updateSlotHandlers] = useDisclosure(false, { onClose: handlers.close }); const medplum = useMedplum(); const profile = useMedplumProfile() as Practitioner; const navigate = useNavigate(); // If a patient is provided, remove the patient question from the questionnaire if (patient) { createAppointmentQuestionnaire.item = createAppointmentQuestionnaire.item?.filter((i) => i.linkId !== 'patient'); } async function handleDeleteSlot(slotId: string): Promise<void> { try { await medplum.deleteResource('Slot', slotId); onAppointmentsUpdated(); showNotification({ icon: <IconCircleCheck />, title: 'Success', message: 'Slot deleted', }); } catch (err) { showNotification({ color: 'red', icon: <IconCircleOff />, title: 'Error', message: normalizeErrorString(err), }); } handlers.close(); } async function handleQuestionnaireSubmit(formData: QuestionnaireResponse): Promise<void> { const answers = getQuestionnaireAnswers(formData); // If a patient is provided to the component, use that patient, otherwise use the patient from the form const appointmentPatient = patient ? createReference(patient) : (answers['patient'].valueReference as Reference<Patient>); try { let appointment: Appointment = { resourceType: 'Appointment', status: 'booked', slot: [createReference(slot as Slot)], appointmentType: { coding: [answers['appointment-type'].valueCoding as Coding] }, serviceType: [{ coding: [answers['service-type'].valueCoding as Coding] }], participant: [ { actor: appointmentPatient, status: 'accepted', }, { actor: createReference(profile), status: 'accepted', }, ], comment: answers['comment']?.valueString, }; // Call bot to create the appointment appointment = await medplum.executeBot({ system: 'http://example.com', value: 'book-appointment' }, appointment); // Navigate to the appointment detail page navigate(`/Appointment/${appointment.id}`)?.catch(console.error); onAppointmentsUpdated(); showNotification({ icon: <IconCircleCheck />, title: 'Success', message: 'Appointment created', }); } catch (err) { showNotification({ color: 'red', icon: <IconCircleOff />, title: 'Error', message: normalizeErrorString(err), }); } handlers.close(); } if (!slot) { return null; } return ( <> <Modal opened={opened} onClose={handlers.close} title={ <Group> <Button variant="subtle" size="compact-md" leftSection={<IconEdit size={16} />} onClick={() => { handlers.close(); updateSlotHandlers.open(); }} > Edit </Button> <Button variant="subtle" size="compact-md" color="red" leftSection={<IconTrash size={16} />} onClick={() => handleDeleteSlot(slot.id as string)} > Delete </Button> </Group> } > <QuestionnaireForm questionnaire={createAppointmentQuestionnaire} onSubmit={handleQuestionnaireSubmit} /> </Modal> <CreateUpdateSlot event={event} opened={updateSlotOpened} handlers={updateSlotHandlers} onSlotsUpdated={onAppointmentsUpdated} /> </> ); } const createAppointmentQuestionnaire: Questionnaire = { resourceType: 'Questionnaire', status: 'active', title: 'Create an Appointment', id: 'new-appointment', item: [ { linkId: 'patient', type: 'reference', text: 'Patient', required: true, extension: [ { url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-referenceResource', valueCodeableConcept: { coding: [ { system: 'http://hl7.org/fhir/fhir-types', code: 'Patient', display: 'Patient', }, ], }, }, ], }, { linkId: 'appointment-type', type: 'choice', text: 'Appointment Type', answerValueSet: 'http://terminology.hl7.org/ValueSet/v2-0276', required: true, }, { linkId: 'service-type', type: 'choice', text: 'Appointment Service Type', answerValueSet: 'http://example.com/appointment-service-types', required: true, }, { linkId: 'comment', type: 'string', text: 'Additional Comments', }, ], };

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