diff --git a/src/components/interactive-builder/add-question.modal.tsx b/src/components/interactive-builder/add-question.modal.tsx index a7e48705d..9e293f487 100644 --- a/src/components/interactive-builder/add-question.modal.tsx +++ b/src/components/interactive-builder/add-question.modal.tsx @@ -149,7 +149,12 @@ const AddQuestionModal: React.FC = ({ const [programWorkflows, setProgramWorkflows] = useState>([]); const [toggleLabelTrue, setToggleLabelTrue] = useState(''); const [toggleLabelFalse, setToggleLabelFalse] = useState(''); - + const [selectedOrders, setSelectedOrders] = useState< + Array<{ + concept: string; + label: string; + }> + >([]); const renderTypeOptions = { encounterDatetime: ['date'], encounterLocation: ['ui-select-extended'], @@ -173,6 +178,7 @@ const AddQuestionModal: React.FC = ({ const handleAnsConceptChange = (event: React.ChangeEvent) => setConceptAnsToLookup(event.target.value); + const handleOrdersChange = (event: React.ChangeEvent) => setConceptToLookup(event.target.value); const handleConceptSelect = (concept: Concept) => { const updatedDatePickerType = getDatePickerType(concept); if (updatedDatePickerType) setDatePickerType(updatedDatePickerType); @@ -195,6 +201,21 @@ const AddQuestionModal: React.FC = ({ }), ); }; + + const handleOrdersSelect = (concept: Concept) => { + const updatedDatePickerType = getDatePickerType(concept); + if (updatedDatePickerType) setDatePickerType(updatedDatePickerType); + setConceptToLookup(''); + setSelectedConcept(concept); + setSelectedOrders([ + ...selectedOrders, + { + concept: concept.uuid, + label: concept.display, + }, + ]); + }; + const handleDeleteAnswer = (id) => { setAddedAnswers((prevAnswers) => prevAnswers.filter((answer) => answer.id !== id)); }; @@ -251,8 +272,8 @@ const AddQuestionModal: React.FC = ({ const computedQuestionId = `question${questionIndex + 1}Section${sectionIndex + 1}Page-${pageIndex + 1}`; const newQuestion = { - ...(questionLabel && {label: questionLabel}), - ...((renderingType === 'markdown') && {value: questionValue}), + ...(questionLabel && { label: questionLabel }), + ...(renderingType === 'markdown' && { value: questionValue }), type: questionType ? questionType : 'control', required: isQuestionRequired, id: questionId ?? computedQuestionId, @@ -295,6 +316,11 @@ const AddQuestionModal: React.FC = ({ labelFalse: toggleLabelFalse, }, }), + ...(questionType === 'testOrder' && { + orderType: 'testOrder', + orderSettingUuid: 'INPATIENT', + selectableOrders: selectedOrders, + }), }, validators: [], }; @@ -314,6 +340,7 @@ const AddQuestionModal: React.FC = ({ setSelectedAnswers([]); setAddObsComment(false); setAddInlineDate(false); + setSelectedOrders([]); showSnackbar({ title: t('success', 'Success!'), @@ -363,14 +390,16 @@ const AddQuestionModal: React.FC = ({ - {renderingType === 'markdown' ? : ( + {renderingType === 'markdown' ? ( + + ) : ( } - placeholder={t('labelPlaceholder', 'e.g. Type of Anaesthesia')} - value={questionLabel} - onChange={(event: React.ChangeEvent) => setQuestionLabel(event.target.value)} - /> + id="questionLabel" + labelText={} + placeholder={t('labelPlaceholder', 'e.g. Type of Anaesthesia')} + value={questionLabel} + onChange={(event: React.ChangeEvent) => setQuestionLabel(event.target.value)} + /> )} = ({ required > {!renderingType && } - {questionTypes.filter((questionType) => !['obs', 'control'].includes(questionType)).includes(questionType as Exclude) + {questionTypes + .filter((questionType) => !['obs', 'control'].includes(questionType)) + .includes(questionType as Exclude) ? renderTypeOptions[questionType].map((type, key) => ( )) - : questionType === 'obs' - ? fieldTypes.filter(type => type !== 'markdown').map((type, key) => ) + : questionType === 'obs' + ? fieldTypes + .filter((type) => type !== 'markdown') + .map((type, key) => ) : fieldTypes.map((type, key) => )} @@ -1000,6 +1033,106 @@ const AddQuestionModal: React.FC = ({ )} )} + + {questionType === 'testOrder' && ( + +
+ + {t('searchForBackingConcept', 'Search for a backing concept')} + + {conceptLookupError ? ( + + ) : null} + { + setSelectedConcept(null); + setDatePickerType('both'); + setAnswers([]); + setConceptMappings([]); + }} + onChange={handleOrdersChange} + placeholder={t('searchConcept', 'Search using a concept name or UUID')} + required + size="md" + value={(() => { + if (conceptToLookup) { + return conceptToLookup; + } + if (selectedConcept) { + return selectedConcept.display; + } + return ''; + })()} + /> + {(() => { + if (!conceptToLookup) return null; + if (isLoadingConcepts) + return ( + + ); + if (concepts?.length && !isLoadingConcepts) { + return ( +
    + {concepts?.map((concept, index) => ( +
  • handleOrdersSelect(concept)} + > + {concept.display} +
  • + ))} +
+ ); + } + return ( + + + + {t('noMatchingConcepts', 'No concepts were found that match')}{' '} + "{debouncedConceptToLookup}". + + + +
+ { +

+ {t('conceptSearchHelpText', "Can't find a concept?")} +

+ } + + {t('searchInOCL', 'Search in OCL')} + + +
+
+ ); + })()} + {selectedOrders.length ? ( +
+ {selectedOrders.map((answer) => ( + + {answer.label} + + ))} +
+ ) : null} +
+
+ )}
@@ -1037,4 +1170,4 @@ function RequiredLabel({ isRequired, text, t }: RequiredLabelProps) { ); } -export default AddQuestionModal; \ No newline at end of file +export default AddQuestionModal; diff --git a/src/components/interactive-builder/edit-question.modal.tsx b/src/components/interactive-builder/edit-question.modal.tsx index 1fe3b4925..7d2de3217 100644 --- a/src/components/interactive-builder/edit-question.modal.tsx +++ b/src/components/interactive-builder/edit-question.modal.tsx @@ -105,7 +105,7 @@ const EditQuestionModal: React.FC = ({ const [max, setMax] = useState(questionToEdit.questionOptions.max ?? ''); const [min, setMin] = useState(questionToEdit.questionOptions.min ?? ''); const [questionId, setQuestionId] = useState(''); - const [questionLabel, setQuestionLabel] = useState(''); + const [questionLabel, setQuestionLabel] = useState(questionToEdit.label); const [questionValue, setQuestionValue] = useState(questionToEdit.value); const [questionType, setQuestionType] = useState(null); const [datePickerType, setDatePickerType] = useState( @@ -165,7 +165,7 @@ const EditQuestionModal: React.FC = ({ const [addInlineDate, setAddInlineDate] = useState(false); const [toggleLabelTrue, setToggleLabelTrue] = useState(questionToEdit?.questionOptions?.toggleOptions?.labelTrue); const [toggleLabelFalse, setToggleLabelFalse] = useState(questionToEdit?.questionOptions?.toggleOptions?.labelFalse); - + const [selectedOrders, setSelectedOrders] = useState(questionToEdit?.questionOptions?.selectableOrders ?? []); // Maps the data type of a concept to a date picker type. const datePickerTypeOptions: Record> = { datetime: [{ value: 'both', label: t('calendarAndTimer', 'Calendar and timer'), defaultChecked: true }], @@ -223,11 +223,32 @@ const EditQuestionModal: React.FC = ({ ); }; + const handleOrdersSelect = (concept: Concept) => { + const datePickerType = getDatePickerType(concept); + if (datePickerType) { + setDatePickerType(datePickerType); + } + setConceptToLookup(''); + setSelectedAnswers([]); + setAddedAnswers([]); + setSelectedOrders([ + ...selectedOrders, + { + concept: concept.uuid, + label: concept.display, + }, + ]); + }; + const handleDeleteAnswer = (id) => { const updatedAnswers = addedAnswers.filter((answer) => answer.id !== id); setAddedAnswers(updatedAnswers); }; + const handleRemoveOrder = (concept) => { + setSelectedOrders(selectedOrders.filter((order) => order.concept !== concept)); + }; + const handleSaveMoreAnswers = () => { const newAnswers = addedAnswers.filter( (newAnswer) => !selectedAnswers.some((prevAnswer) => prevAnswer.id === newAnswer.id), @@ -334,7 +355,7 @@ const EditQuestionModal: React.FC = ({ concept: selectedConcept ? selectedConcept.uuid : questionToEdit.questionOptions.concept, conceptMappings: conceptMappings?.length ? conceptMappings : questionToEdit.questionOptions.conceptMappings, }), - answers: mappedAnswers, + ...(mappedAnswers && { answers: mappedAnswers }), ...(questionType === 'patientIdentifier' && { identifierType: selectedPatientIdentifierType ? selectedPatientIdentifierType['uuid'] @@ -348,9 +369,11 @@ const EditQuestionModal: React.FC = ({ ...(addInlineDate && { showDate: addInlineDate ? addInlineDate : /true/.test(questionToEdit.questionOptions.showDate.toString()), }), - attributeType: selectedPersonAttributeType - ? selectedPersonAttributeType['uuid'] - : questionToEdit.questionOptions.attributeType, + ...((selectedPersonAttributeType || questionToEdit.questionOptions.attributeType) && { + attributeType: selectedPersonAttributeType + ? selectedPersonAttributeType['uuid'] + : questionToEdit.questionOptions.attributeType, + }), ...(selectedProgram && { programUuid: selectedProgram.uuid }), ...(programWorkflow && { workflowUuid: programWorkflow.uuid }), ...(fieldType === 'toggle' && { @@ -359,6 +382,11 @@ const EditQuestionModal: React.FC = ({ labelFalse: toggleLabelFalse, }, }), + ...(questionToEdit.type === 'testOrder' && { + orderType: 'testOrder', + orderSettingUuid: 'INPATIENT', + selectableOrders: selectedOrders, + }), }, }; @@ -375,6 +403,7 @@ const EditQuestionModal: React.FC = ({ setSelectedAnswers([]); setAddObsComment(false); setAddInlineDate(false); + setSelectedOrders([]); showSnackbar({ title: t('questionEdited', 'Question edited'), @@ -1085,6 +1114,85 @@ const EditQuestionModal: React.FC = ({ ))} ) : null} + + {questionToEdit.type === 'testOrder' && ( + <> + { + setSelectedConcept(null); + setDatePickerType('both'); + }} + onChange={(e: React.ChangeEvent) => handleConceptChange(e.target.value?.trim())} + placeholder={t('searchConcept', 'Search using a concept name or UUID')} + required + size="md" + value={selectedConcept?.display} + /> + {(() => { + if (!conceptToLookup) return null; + if (isLoadingConcepts) + return ( + + ); + if (concepts?.length && !isLoadingConcepts) { + return ( +
    + {concepts?.map((concept, index) => ( +
  • handleOrdersSelect(concept)} + > + {concept.display} +
  • + ))} +
+ ); + } + return ( + + + + {t('noMatchingConcepts', 'No concepts were found that match')}{' '} + "{conceptToLookup}". + + + +
+ {

{t('conceptSearchHelpText', "Can't find a concept?")}

} + + {t('searchInOCL', 'Search in OCL')} + + +
+
+ ); + })()} + {selectedOrders.length ? ( +
+ {selectedOrders.map((answer) => ( + handleRemoveOrder(answer.concept)} + type="blue" + > + {answer.label} + + ))} +
+ ) : null} + + )}