import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import classNames from 'classnames'; import dayjs from 'dayjs'; import { Controller, useFormContext, useWatch } from 'react-hook-form'; import { Button, ButtonSet, ComboBox, ContentSwitcher, Form, FormGroup, InlineLoading, InlineNotification, NumberInput, Stack, Switch, TextArea, } from '@carbon/react'; import { ResponsiveWrapper, showSnackbar, useConfig, useLayoutType } from '@openmrs/esm-framework'; import type { ConfigObject } from '../../config-schema'; import { saveProcedure, updateProcedure, useConceptSearch, useConceptSearchField, useMutatePatientProcedures, useProcedureTypes, } from '../../procedures.resource'; import type { ConceptReference, Procedure, ProcedureType } from '../../types'; import type { ProceduresFormSchema } from './procedures-form.workspace'; import { ConceptSearchField } from '../../components/concept-search-field/concept-search-field.component'; import { DateTimeField } from '../../components/date-time-field/date-time-field.component'; import styles from './procedures-form.scss'; type ProceduresFormComponentProps = { closeWorkspace: () => void; procedure?: Procedure; patientUuid: string; }; const ProceduresFormComponent: React.FC = ({ closeWorkspace, patientUuid, procedure, }) => { const { t } = useTranslation(); const { procedureConceptUuid, procedureConceptSourceType, bodySiteConceptUuid, bodySiteConceptSourceType, statusConceptUuid, statusConceptSourceType, durationUnitConceptUuid, durationUnitConceptSourceType, } = useConfig(); const mutate = useMutatePatientProcedures(patientUuid); const { handleSubmit, control, formState: { errors, isSubmitted }, getValues, setValue, trigger, } = useFormContext(); const { procedureTypes, isLoading: isLoadingProcedureTypes } = useProcedureTypes(); const today = useMemo(() => new Date(), []); const watchedStartDateTime = useWatch({ control, name: 'startDateTime' }); const endDateMin = useMemo(() => { if (!watchedStartDateTime) return undefined; const startOfDay = new Date(watchedStartDateTime); startOfDay.setHours(0, 0, 0, 0); return startOfDay; }, [watchedStartDateTime]); const isTablet = useLayoutType() === 'tablet'; const [isSubmittingForm, setIsSubmittingForm] = useState(false); const [isStartDateKnown, setIsStartDateKnown] = useState(!getValues('estimatedStartDate')); const initialEstimatedDate = getValues('estimatedStartDate'); const [estimatedYear, setEstimatedYear] = useState(initialEstimatedDate?.split('-')[0] ?? ''); const [estimatedMonth, setEstimatedMonth] = useState(initialEstimatedDate?.split('-')[1] ?? ''); useEffect(() => { const value = !isStartDateKnown && estimatedYear ? (estimatedMonth ? `${estimatedYear}-${estimatedMonth}` : estimatedYear) : ''; setValue('estimatedStartDate', value); if (isSubmitted) { trigger('startDateTime'); } }, [isStartDateKnown, estimatedYear, estimatedMonth, setValue, isSubmitted, trigger]); const yearOptions = useMemo(() => { const currentYear = new Date().getFullYear(); return Array.from({ length: currentYear - 1900 + 1 }, (_, i) => ({ id: String(currentYear - i), label: String(currentYear - i), })); }, []); const monthOptions = useMemo( () => [ { id: '01', label: t('january', 'January') }, { id: '02', label: t('february', 'February') }, { id: '03', label: t('march', 'March') }, { id: '04', label: t('april', 'April') }, { id: '05', label: t('may', 'May') }, { id: '06', label: t('june', 'June') }, { id: '07', label: t('july', 'July') }, { id: '08', label: t('august', 'August') }, { id: '09', label: t('september', 'September') }, { id: '10', label: t('october', 'October') }, { id: '11', label: t('november', 'November') }, { id: '12', label: t('december', 'December') }, ], [t], ); const procedureField = useConceptSearchField({ uuid: procedureConceptUuid, sourceType: procedureConceptSourceType }); const bodySiteField = useConceptSearchField({ uuid: bodySiteConceptUuid, sourceType: bodySiteConceptSourceType }); const [procedureConcept, setProcedureConcept] = useState( procedure?.procedureCoded?.uuid ? procedure.procedureCoded : null, ); const [bodySiteConcept, setBodySiteConcept] = useState( procedure?.bodySite?.uuid ? procedure.bodySite : null, ); const { searchResults: statusOptions, error: statusOptionsError } = useConceptSearch('', { uuid: statusConceptUuid, sourceType: statusConceptSourceType, }); const { searchResults: durationUnitOptions, error: durationUnitOptionsError } = useConceptSearch('', { uuid: durationUnitConceptUuid, sourceType: durationUnitConceptSourceType, }); const [errorSaving, setErrorSaving] = useState(null); const handleSave = useCallback(async () => { setIsSubmittingForm(true); const procedureType = getValues('procedureType'); const startDateTime = getValues('startDateTime'); const endDateTime = getValues('endDateTime'); const notes = getValues('notes'); const duration = getValues('duration'); const durationUnit = getValues('durationUnit'); const estimatedStartDate = getValues('estimatedStartDate'); const hasDuration = typeof duration === 'number' && !Number.isNaN(duration); const payload = { patient: patientUuid, procedureCoded: getValues('procedureCoded'), procedureType: procedureType, bodySite: getValues('bodySite') || null, startDateTime: startDateTime ? dayjs(startDateTime).format() : null, endDateTime: endDateTime ? dayjs(endDateTime).format() : null, status: getValues('status'), notes: notes, estimatedStartDate: estimatedStartDate || null, duration: hasDuration ? duration : null, durationUnit: hasDuration && durationUnit ? durationUnit : null, }; try { if (procedure?.uuid) { await updateProcedure(procedure.uuid, payload); } else { await saveProcedure(payload); } await mutate(); showSnackbar({ kind: 'success', title: procedure?.uuid ? t('procedureUpdated', 'Procedure updated') : t('procedureSaved', 'Procedure saved'), subtitle: procedure?.uuid ? t('procedureUpdatesNowVisible', 'Changes to the procedure are now visible on the Procedures page') : t('procedureNowVisible', 'It is now visible on the Procedures page'), }); closeWorkspace(); } catch (error) { setIsSubmittingForm(false); setErrorSaving(error); } }, [closeWorkspace, getValues, mutate, patientUuid, procedure?.uuid, t]); const onError = () => setIsSubmittingForm(false); return (
}> { setProcedureConcept(concept); setValue('procedureCoded', concept?.uuid ?? '', { shouldValidate: true }); }} invalid={Boolean(errors.procedureCoded)} invalidText={errors.procedureCoded?.message} /> }> {isLoadingProcedureTypes ? ( ) : ( item?.name ?? ''} initialSelectedItem={procedureTypes.find((pt) => pt.uuid === getValues('procedureType')) ?? null} onChange={({ selectedItem }: { selectedItem: ProcedureType | null }) => setValue('procedureType', selectedItem?.uuid ?? '', { shouldValidate: true }) } invalid={Boolean(errors.procedureType)} invalidText={errors.procedureType?.message} /> )} }> { setBodySiteConcept(concept); setValue('bodySite', concept?.uuid ?? '', { shouldValidate: true }); }} invalid={Boolean(errors.bodySite)} invalidText={errors.bodySite?.message} /> { const isKnown = index === 0; setIsStartDateKnown(isKnown); if (!isKnown) { setValue('startDateTime', null); } setEstimatedYear(''); setEstimatedMonth(''); }} > {t('yes', 'Yes')} {t('no', 'No')} {isStartDateKnown && ( }> ( )} /> )} {!isStartDateKnown && (
} placeholder={t('selectYear', 'Select year')} items={yearOptions} itemToString={(item: { id: string; label: string }) => item?.label ?? ''} selectedItem={yearOptions.find((y) => y.id === estimatedYear) ?? null} onChange={({ selectedItem }: { selectedItem: { id: string; label: string } | null }) => setEstimatedYear(selectedItem?.id ?? '') } invalid={Boolean(errors.startDateTime)} invalidText={errors.startDateTime?.message} /> item?.label ?? ''} selectedItem={monthOptions.find((m) => m.id === estimatedMonth) ?? null} onChange={({ selectedItem }: { selectedItem: { id: string; label: string } | null }) => setEstimatedMonth(selectedItem?.id ?? '') } />
)} ( )} />
( { if (nextValue == null || nextValue === '') { onChange(null); return; } const parsed = typeof nextValue === 'number' ? nextValue : Number(nextValue); onChange(Number.isNaN(parsed) ? null : parsed); }} /> )} /> ( item?.display ?? ''} selectedItem={durationUnitOptions.find((option) => option.uuid === field.value) ?? null} onChange={({ selectedItem }: { selectedItem: ConceptReference | null }) => field.onChange(selectedItem?.uuid ?? null) } invalid={Boolean(fieldState.error)} invalidText={fieldState.error?.message} /> )} /> {durationUnitOptionsError && (

{t('durationUnitOptionsLoadFailed', 'Could not load duration unit options. Please try again.')}

)}
}> ( item?.display ?? ''} selectedItem={statusOptions.find((option) => option.uuid === field.value) ?? null} onChange={({ selectedItem }: { selectedItem: ConceptReference | null }) => field.onChange(selectedItem?.uuid ?? null) } invalid={Boolean(fieldState.error)} invalidText={fieldState.error?.message} /> )} /> {statusOptionsError && (

{t('statusOptionsLoadFailed', 'Could not load status options. Please try again.')}

)}
(