Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat) O3-3712: add a concept answer to a concept in the form builder #351

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 165 additions & 5 deletions src/components/interactive-builder/add-question.modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ interface AddQuestionModalProps {
interface Item {
text: string;
}

interface ProgramStateData {
selectedItems: Array<ProgramState>;
}
Expand Down Expand Up @@ -100,12 +99,15 @@ const AddQuestionModal: React.FC<AddQuestionModalProps> = ({
const [answers, setAnswers] = useState<Array<Answer>>([]);
const [conceptMappings, setConceptMappings] = useState<Array<ConceptMapping>>([]);
const [conceptToLookup, setConceptToLookup] = useState('');
const [conceptAnsToLookup, setConceptAnsToLookup] = useState('');
const debouncedAnsConceptToLookup = useDebounce(conceptAnsToLookup);
const debouncedConceptToLookup = useDebounce(conceptToLookup);
const [datePickerType, setDatePickerType] = useState<DatePickerType>('both');
const [renderingType, setRenderingType] = useState<RenderType | null>(null);
const [isQuestionRequired, setIsQuestionRequired] = useState(false);
const [max, setMax] = useState('');
const [min, setMin] = useState('');
const [addAnswer, setAnswer] = useState(false);
const [questionId, setQuestionId] = useState('');
const [questionLabel, setQuestionLabel] = useState('');
const [questionType, setQuestionType] = useState<QuestionType | null>(null);
Expand All @@ -116,9 +118,21 @@ const AddQuestionModal: React.FC<AddQuestionModalProps> = ({
text: string;
}>
>([]);
const [addedAnswers, setAddedAnswers] = useState<
Array<{
id: string;
text: string;
}>
>([]);
const [selectedConcept, setSelectedConcept] = useState<Concept | null>(null);
const [selectedAnsConcept, setSelectedAnsConcept] = useState<Concept | null>(null);
const [selectedPersonAttributeType, setSelectedPersonAttributeType] = useState<PersonAttributeType | null>(null);
const { concepts, conceptLookupError, isLoadingConcepts } = useConceptLookup(debouncedConceptToLookup);
const {
concepts: ansConcepts,
conceptLookupError: conceptAnsLookupError,
isLoadingConcepts: isLoadingAnsConcepts,
} = useConceptLookup(debouncedAnsConceptToLookup);
const { personAttributeTypes, personAttributeTypeLookupError } = usePersonAttributeTypes();
const [selectedPatientIdetifierType, setSelectedPatientIdetifierType] = useState<PatientIdentifierType>(null);
const { patientIdentifierTypes, patientIdentifierTypeLookupError } = usePatientIdentifierTypes();
Expand Down Expand Up @@ -156,10 +170,13 @@ const AddQuestionModal: React.FC<AddQuestionModalProps> = ({
};

const handleConceptChange = (event: React.ChangeEvent<HTMLInputElement>) => setConceptToLookup(event.target.value);
const handleAnsConceptChange = (event: React.ChangeEvent<HTMLInputElement>) =>
setConceptAnsToLookup(event.target.value);

const handleConceptSelect = (concept: Concept) => {
const updatedDatePickerType = getDatePickerType(concept);
if (updatedDatePickerType) setDatePickerType(updatedDatePickerType);
setAnswer(false);
setConceptToLookup('');
setSelectedConcept(concept);
setAnswers(
Expand All @@ -179,6 +196,30 @@ const AddQuestionModal: React.FC<AddQuestionModalProps> = ({
}),
);
};
const handleDeleteAnswer = (id) => {
setAddedAnswers((prevAnswers) => prevAnswers.filter((answer) => answer.id !== id));
};
const handleSaveMoreAnswers = () => {
const newAnswers = addedAnswers.filter(
(newAnswer) => !selectedAnswers.some((prevAnswer) => prevAnswer.id === newAnswer.id),
);

const updatedAnswers = [...selectedAnswers, ...newAnswers];
setSelectedAnswers(updatedAnswers);
setAddedAnswers([]);
return updatedAnswers;
};

const handleConceptAnsSelect = (concept: Concept) => {
setConceptAnsToLookup('');
setSelectedAnsConcept(concept);
const newAnswer = { id: concept.uuid, text: concept.display };
const answerExistsInSelected = selectedAnswers.some((answer) => answer.id === newAnswer.id);
const answerExistsInAdded = addedAnswers.some((answer) => answer.id === newAnswer.id);
if (!answerExistsInSelected && !answerExistsInAdded) {
setAddedAnswers((prevAnswers) => [...prevAnswers, newAnswer]);
}
};

const handlePersonAttributeTypeChange = ({ selectedItem }: { selectedItem: PersonAttributeType }) => {
setSelectedPersonAttributeType(selectedItem);
Expand All @@ -199,13 +240,13 @@ const AddQuestionModal: React.FC<AddQuestionModalProps> = ({
const questionIds: Array<string> = flattenDeep(nestedIds);
return questionIds.includes(idToTest);
};

const handleCreateQuestion = () => {
createQuestion();
const updatedAnswers = handleSaveMoreAnswers();
createQuestion(updatedAnswers);
closeModal();
};

const createQuestion = () => {
const createQuestion = (selectedAnswers) => {
Willie-theBeastMutua marked this conversation as resolved.
Show resolved Hide resolved
try {
const questionIndex = schema.pages[pageIndex]?.sections?.[sectionIndex]?.questions?.length ?? 0;
const computedQuestionId = `question${questionIndex + 1}Section${sectionIndex + 1}Page-${pageIndex + 1}`;
Expand Down Expand Up @@ -301,6 +342,14 @@ const AddQuestionModal: React.FC<AddQuestionModalProps> = ({
setQuestionId(camelCasedLabel);
};

const showAddQuestion = () => {
if (!addAnswer) {
setAnswer(true);
return;
}
setAnswer(false);
};

const handleProgramWorkflowChange = (selectedItem: ProgramWorkflow) => {
setProgramWorkflow(selectedItem);
void mutateProgramStates();
Expand Down Expand Up @@ -688,7 +737,6 @@ const AddQuestionModal: React.FC<AddQuestionModalProps> = ({
titleText={t('selectAnswersToDisplay', 'Select answers to display')}
/>
) : null}

{selectedAnswers.length ? (
<div>
{selectedAnswers.map((answer) => (
Expand All @@ -698,6 +746,118 @@ const AddQuestionModal: React.FC<AddQuestionModalProps> = ({
))}
</div>
) : null}
{selectedConcept && answers?.length ? (
<div>
<Button kind="tertiary" onClick={showAddQuestion} iconDescription="Add" size="sm">
More Answers
</Button>
</div>
) : null}
{addAnswer ? (
<div>
<FormLabel className={styles.label}>
{t('searchForAnswerConcept', 'Search for an answer Concept to Add')}
</FormLabel>
{conceptAnsLookupError ? (
<InlineNotification
kind="error"
lowContrast
className={styles.error}
title={t('errorFetchingConcepts', 'Error fetching concepts')}
subtitle={t('pleaseTryAgain', 'Please try again.')}
/>
) : null}
<Search
id="conceptAnsLookup"
onClear={() => {
setSelectedAnsConcept(null);
}}
onChange={handleAnsConceptChange}
placeholder={t('searchConcept', 'Search using a concept name or UUID')}
required
size="md"
value={(() => {
if (conceptAnsToLookup) {
return conceptAnsToLookup;
}
if (selectedAnsConcept) {
return selectedAnsConcept.display;
}
return '';
})()}
/>
{addedAnswers.length > 0 ? (
<div>
{addedAnswers.map((answer) => (
<Tag className={styles.tag} key={answer.id} type={'blue'}>
{answer.text}
<button
className={styles.conceptAnswerButton}
onClick={() => handleDeleteAnswer(answer.id)}
>
X
</button>
</Tag>
))}
</div>
) : null}

{(() => {
if (!conceptAnsToLookup) return null;
if (isLoadingAnsConcepts)
return (
<InlineLoading
className={styles.loader}
description={t('searching', 'Searching') + '...'}
/>
);
if (ansConcepts?.length && !isLoadingAnsConcepts) {
return (
<ul className={styles.conceptList}>
{ansConcepts?.map((concept, index) => (
<li
role="menuitem"
className={styles.concept}
key={index}
onClick={() => handleConceptAnsSelect(concept)}
>
{concept.display}
</li>
))}
</ul>
);
}

return (
<Layer>
<Tile className={styles.emptyResults}>
<span>
{t('noMatchingConcepts', 'No concepts were found that match')}{' '}
<strong>"{debouncedAnsConceptToLookup}".</strong>
</span>
</Tile>

<div className={styles.oclLauncherBanner}>
{
<p className={styles.bodyShort01}>
{t('conceptSearchHelpText', "Can't find a concept?")}
</p>
}
<a
className={styles.oclLink}
target="_blank"
rel="noopener noreferrer"
href={'https://app.openconceptlab.org/'}
>
{t('searchInOCL', 'Search in OCL')}
<ArrowUpRight size={16} />
</a>
</div>
</Layer>
);
})()}
</div>
) : null}

<Stack gap={5}>
<RadioButtonGroup
Expand Down
Loading
Loading