Skip to content

Commit

Permalink
feat: Add support from markdown questions
Browse files Browse the repository at this point in the history
  • Loading branch information
Twiineenock committed Oct 14, 2024
1 parent 6e7fb92 commit 5a50c2b
Show file tree
Hide file tree
Showing 11 changed files with 629 additions and 161 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@
"fuzzy": "^0.1.3",
"lodash-es": "^4.17.21",
"react-ace": "^11.0.1",
"react-markdown": "^9.0.1",
"react-mde": "^11.5.0",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.0",
"sass": "^1.67.0"
},
"peerDependencies": {
Expand Down
139 changes: 74 additions & 65 deletions src/components/interactive-builder/add-question.modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { useConceptLookup } from '../../hooks/useConceptLookup';
import { usePatientIdentifierTypes } from '../../hooks/usePatientIdentifierTypes';
import { usePersonAttributeTypes } from '../../hooks/usePersonAttributeTypes';
import { useProgramWorkStates, usePrograms } from '../../hooks/useProgramStates';
import MarkdownQuestion from './markdown-question.component';
import styles from './question-modal.scss';

interface AddQuestionModalProps {
Expand Down Expand Up @@ -108,6 +109,7 @@ const AddQuestionModal: React.FC<AddQuestionModalProps> = ({
const [min, setMin] = useState('');
const [questionId, setQuestionId] = useState('');
const [questionLabel, setQuestionLabel] = useState('');
const [questionValue, setQuestionValue] = useState('');
const [questionType, setQuestionType] = useState<QuestionType | null>(null);
const [rows, setRows] = useState('');
const [selectedAnswers, setSelectedAnswers] = useState<
Expand Down Expand Up @@ -211,8 +213,9 @@ const AddQuestionModal: React.FC<AddQuestionModalProps> = ({
const computedQuestionId = `question${questionIndex + 1}Section${sectionIndex + 1}Page-${pageIndex + 1}`;

const newQuestion = {
label: questionLabel,
type: questionType,
...(questionLabel && {label: questionLabel}),
...((renderingType === 'markdown') && {value: questionValue}),
...((renderingType !== 'markdown') && {type: questionType}),
required: isQuestionRequired,
id: questionId ?? computedQuestionId,
...((renderingType === 'date' || renderingType === 'datetime') &&
Expand Down Expand Up @@ -320,14 +323,35 @@ const AddQuestionModal: React.FC<AddQuestionModalProps> = ({
<ModalBody hasScrollingContent>
<FormGroup legendText={''}>
<Stack gap={5}>
<TextInput
<Select
value={renderingType}
onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
setRenderingType(event.target.value as RenderType)
}
id="renderingType"
invalidText={t('validRenderingTypeRequired', 'A valid rendering type value is required')}
labelText={t('renderingType', 'Rendering type')}
required
>
{!renderingType && <SelectItem text={t('chooseRenderingType', 'Choose a rendering type')} value="" />}

{questionTypes.filter((questionType) => questionType !== 'obs').includes(questionType as Exclude<QuestionType, 'obs'>)
? renderTypeOptions[questionType].map((type, key) => (
<SelectItem key={`${questionType}-${key}`} text={type} value={type} />
))
: fieldTypes.map((type, key) => <SelectItem key={key} text={type} value={type} />)}
</Select>

{renderingType === 'markdown' ? <MarkdownQuestion onValueChange={setQuestionValue}/> : (
<TextInput
id="questionLabel"
labelText={<RequiredLabel isRequired={isQuestionRequired} text={t('questionLabel', 'Label')} t={t} />}
placeholder={t('labelPlaceholder', 'e.g. Type of Anaesthesia')}
value={questionLabel}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => setQuestionLabel(event.target.value)}
required
/>
)}

<TextInput
id="questionId"
Expand Down Expand Up @@ -359,64 +383,48 @@ const AddQuestionModal: React.FC<AddQuestionModalProps> = ({
required
/>

<RadioButtonGroup
defaultSelected="optional"
name="isQuestionRequired"
legendText={t(
'isQuestionRequiredOrOptional',
'Is this question a required or optional field? Required fields must be answered before the form can be submitted.',
)}
>
<RadioButton
id="questionIsNotRequired"
defaultChecked={true}
labelText={t('optional', 'Optional')}
onClick={() => setIsQuestionRequired(false)}
value="optional"
/>
<RadioButton
id="questionIsRequired"
defaultChecked={false}
labelText={t('required', 'Required')}
onClick={() => setIsQuestionRequired(true)}
value="required"
/>
</RadioButtonGroup>

<Select
value={questionType}
onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
setQuestionType(event.target.value as QuestionType)
}
id="questionType"
invalidText={t('typeRequired', 'Type is required')}
labelText={t('questionType', 'Question type')}
required
>
{!questionType && <SelectItem text={t('chooseQuestionType', 'Choose a question type')} value="" />}
{questionTypes.map((questionType, key) => (
<SelectItem text={questionType} value={questionType} key={key} />
))}
</Select>

<Select
value={renderingType}
onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
setRenderingType(event.target.value as RenderType)
}
id="renderingType"
invalidText={t('validRenderingTypeRequired', 'A valid rendering type value is required')}
labelText={t('renderingType', 'Rendering type')}
required
>
{!renderingType && <SelectItem text={t('chooseRenderingType', 'Choose a rendering type')} value="" />}

{questionTypes.filter((questionType) => questionType !== 'obs').includes(questionType)
? renderTypeOptions[questionType].map((type, key) => (
<SelectItem key={`${questionType}-${key}`} text={type} value={type} />
))
: fieldTypes.map((type, key) => <SelectItem key={key} text={type} value={type} />)}
</Select>
{renderingType !== 'markdown' && (
<>
<RadioButtonGroup
defaultSelected="optional"
name="isQuestionRequired"
legendText={t(
'isQuestionRequiredOrOptional',
'Is this question a required or optional field? Required fields must be answered before the form can be submitted.',
)}
>
<RadioButton
id="questionIsNotRequired"
defaultChecked={true}
labelText={t('optional', 'Optional')}
onClick={() => setIsQuestionRequired(false)}
value="optional"
/>
<RadioButton
id="questionIsRequired"
defaultChecked={false}
labelText={t('required', 'Required')}
onClick={() => setIsQuestionRequired(true)}
value="required"
/>
</RadioButtonGroup>
<Select
value={questionType}
onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
setQuestionType(event.target.value as QuestionType)
}
id="questionType"
invalidText={t('typeRequired', 'Type is required')}
labelText={t('questionType', 'Question type')}
required
>
{!questionType && <SelectItem text={t('chooseQuestionType', 'Choose a question type')} value="" />}
{questionTypes.map((questionType, key) => (
<SelectItem text={questionType} value={questionType} key={key} />
))}
</Select>
</>
)}

{questionType === 'personAttribute' && (
<div>
Expand Down Expand Up @@ -846,11 +854,12 @@ const AddQuestionModal: React.FC<AddQuestionModalProps> = ({
</Button>
<Button
disabled={
!questionLabel ||
!questionId ||
((!questionLabel ||
questionIdExists(questionId) ||
!renderingType ||
(questionType === 'patientIdentifier' && !selectedPatientIdetifierType)
(questionType === 'patientIdentifier' && !selectedPatientIdetifierType)) &&
renderingType !== 'markdown') ||
!questionId
}
onClick={handleCreateQuestion}
>
Expand All @@ -874,4 +883,4 @@ function RequiredLabel({ isRequired, text, t }: RequiredLabelProps) {
);
}

export default AddQuestionModal;
export default AddQuestionModal;
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import React, { useCallback } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeRaw from 'rehype-raw';
import classNames from 'classnames';
import { useDraggable } from '@dnd-kit/core';
import { CSS } from '@dnd-kit/utilities';
Expand Down Expand Up @@ -69,6 +72,18 @@ const DraggableQuestion: React.FC<DraggableQuestionProps> = ({
}
}, [handleDuplicateQuestion, isDragging, question, pageIndex, sectionIndex]);

const MarkdownWrapper: React.FC<{ markdown: string | Array<string> }> = ({ markdown }) => {
return (
<ReactMarkdown
children={Array.isArray(markdown) ? markdown.join('\n') : markdown}
unwrapDisallowed={true}
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw]}
allowedElements={['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'strong', 'em', 'ul', 'ol', 'li', 'input', 'sup', 'sub', 'del']}
/>
);
};

return (
<div
className={classNames({
Expand All @@ -89,7 +104,9 @@ const DraggableQuestion: React.FC<DraggableQuestionProps> = ({
<Draggable />
</IconButton>
</div>
<p className={styles.questionLabel}>{question.label}</p>
<p className={styles.questionLabel}>
<MarkdownWrapper markdown={question.label || question.value} />
</p>
</div>
<div className={styles.buttonsContainer}>
<CopyButton
Expand Down
2 changes: 1 addition & 1 deletion src/components/interactive-builder/draggable-question.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

.dragContainer {
display: flex;
height: 3rem;
min-height: 3rem;
justify-content: space-between;
align-items: center;
width: 100%;
Expand Down
Loading

0 comments on commit 5a50c2b

Please sign in to comment.