import React, { type ChangeEvent, useCallback, useEffect, useMemo } from 'react'; import classNames from 'classnames'; import { Button, ButtonSet, Column, ComboBox, Form, Grid, Layer, Select, SelectItem, TextArea, TextInput, } from '@carbon/react'; import { Controller, type ControllerRenderProps, type FieldErrors, useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { useTranslation } from 'react-i18next'; import { type OrderUrgency, priorityOptions, useOrderBasket, useOrderType, type TestOrderBasketItem, postOrder, useMutatePatientOrders, showOrderSuccessToast, } from '@openmrs/esm-patient-common-lib'; import { ExtensionSlot, OpenmrsDatePicker, showSnackbar, useConfig, useLayoutType, type Workspace2DefinitionProps, } from '@openmrs/esm-framework'; import { prepTestOrderPostData, useOrderReasons } from '../api'; import { ordersEqual } from './test-order'; import { type ConfigObject } from '../../config-schema'; import styles from './test-order-form.scss'; export interface LabOrderFormProps { closeWorkspace: Workspace2DefinitionProps['closeWorkspace']; initialOrder: TestOrderBasketItem; onCancel: () => void; /** * This field should only be supplied for an existing order saved to the backend */ orderToEditOrdererUuid?: string; orderTypeUuid: string; orderableConceptSets: Array; setHasUnsavedChanges: (hasUnsavedChanges: boolean) => void; patient: fhir.Patient; } // Designs: // https://app.zeplin.io/project/60d5947dd636aebbd63dce4c/screen/640b06c440ee3f7af8747620 // https://app.zeplin.io/project/60d5947dd636aebbd63dce4c/screen/640b06d286e0aa7b0316db4a export function LabOrderForm({ initialOrder, orderToEditOrdererUuid, closeWorkspace, onCancel, orderTypeUuid, setHasUnsavedChanges, patient, }: LabOrderFormProps) { const { t } = useTranslation(); const isTablet = useLayoutType() === 'tablet'; const { orders, setOrders, clearOrders } = useOrderBasket( patient, orderTypeUuid, prepTestOrderPostData, ); const config = useConfig(); const { orderType } = useOrderType(orderTypeUuid); const { mutate: mutateOrders } = useMutatePatientOrders(patient.id); const orderReasonRequired = useMemo( () => (config.labTestsWithOrderReasons?.find((c) => c.labTestUuid === initialOrder?.testType?.conceptUuid) || {}) .required, [config.labTestsWithOrderReasons, initialOrder?.testType?.conceptUuid], ); const labOrderFormSchema = useMemo( () => z .object({ instructions: z.string().nullish(), urgency: z.string().refine((value) => value !== '', { message: t('priorityRequired', 'Priority is required'), }), accessionNumber: z.string().nullish(), testType: z.object( { label: z.string(), conceptUuid: z.string() }, { required_error: t('testTypeRequired', 'Test type is required'), invalid_type_error: t('testTypeRequired', 'Test type is required'), }, ), orderReason: orderReasonRequired ? z .string({ required_error: t('orderReasonRequired', 'Order reason is required'), }) .refine((value) => !!value, t('orderReasonRequired', 'Order reason is required')) : z.string().optional(), scheduledDate: z.date({}).nullish(), }) .refine((data) => data.urgency !== 'ON_SCHEDULED_DATE' || Boolean(data.scheduledDate), { message: t('scheduledDateRequired', 'Scheduled date is required'), path: ['scheduledDate'], }), [orderReasonRequired, t], ); const { control, handleSubmit, formState: { errors, defaultValues, isDirty, isSubmitting }, setValue, watch, } = useForm({ mode: 'all', resolver: zodResolver(labOrderFormSchema), defaultValues: { accessionNumber: null, ...initialOrder, }, }); const isScheduledDateRequired = watch('urgency') === 'ON_SCHEDULED_DATE'; const orderReasonUuids = (config.labTestsWithOrderReasons?.find((c) => c.labTestUuid === defaultValues?.testType?.conceptUuid) || {}) .orderReasons || []; const { orderReasons } = useOrderReasons(orderReasonUuids); const filterItemsByName = useCallback((menu) => { const inputValue = menu?.inputValue?.toLowerCase(); const itemDisplay = menu?.item?.display?.toLowerCase(); if (!inputValue) { return true; } return itemDisplay?.includes(inputValue); }, []); const saveLabOrderToBasket = useCallback( (data: TestOrderBasketItem) => { const finalizedOrder: TestOrderBasketItem = { ...initialOrder, ...data, }; const newOrders = [...orders]; const existingOrder = orders.find((order) => ordersEqual(order, finalizedOrder)); if (existingOrder) { newOrders[orders.indexOf(existingOrder)] = { ...finalizedOrder, // Incomplete orders should be marked completed on saving the form isOrderIncomplete: false, }; } else { newOrders.push(finalizedOrder); } setOrders(newOrders); closeWorkspace({ discardUnsavedChanges: true }); }, [orders, setOrders, closeWorkspace, initialOrder], ); const submitLabOrderToServer = useCallback( (data: TestOrderBasketItem) => { const finalizedOrder: TestOrderBasketItem = { ...initialOrder, ...data, }; return postOrder( prepTestOrderPostData(finalizedOrder, patient.id, finalizedOrder?.encounterUuid, orderToEditOrdererUuid), ) .then(() => { clearOrders(); mutateOrders(); /* Translation keys used by showOrderSuccessToast: * t('ordersCompleted', 'Orders completed') * t('orderPlaced', 'Order placed') * t('ordersPlaced', 'Orders placed') * t('orderUpdated', 'Order updated') * t('ordersUpdated', 'Orders updated') * t('orderDiscontinued', 'Order discontinued') * t('ordersDiscontinued', 'Orders discontinued') * t('orderedFor', 'Placed order for') * t('updated', 'Updated') * t('discontinued', 'Discontinued') */ showOrderSuccessToast('@openmrs/esm-patient-tests-app', [finalizedOrder]); closeWorkspace({ discardUnsavedChanges: true }); }) .catch((error) => { showSnackbar({ isLowContrast: false, kind: 'error', title: t('errorSavingLabOrder', 'Error saving lab order'), subtitle: error.message, }); }); }, [clearOrders, closeWorkspace, initialOrder, mutateOrders, patient.id, orderToEditOrdererUuid, t], ); const onError = (errors: FieldErrors) => { console.error('Error in lab order form', errors); }; const handleUpdateUrgency = useCallback( (fieldOnChange: ControllerRenderProps['onChange']) => { return (e: ChangeEvent) => { const value = e.target.value as OrderUrgency; if (value !== 'ON_SCHEDULED_DATE') { setValue('scheduledDate', null); } fieldOnChange(e); }; }, [setValue], ); useEffect(() => { setHasUnsavedChanges(isDirty); }, [isDirty, setHasUnsavedChanges]); const responsiveSize = isTablet ? 'lg' : 'sm'; return (

{initialOrder?.testType?.label}

{config.showReferenceNumberField ? ( ( )} /> ) : null} ( )} /> {isScheduledDateRequired && ( ( )} /> )} {orderReasons.length > 0 && ( ( item?.display} onBlur={onBlur} onChange={({ selectedItem }) => onChange(selectedItem?.uuid || '')} selectedItem={null} shouldFilterItem={filterItemsByName} size={responsiveSize} titleText={t('orderReason', 'Order reason')} /> )} /> )} (