Loading editor/package.json +2 −2 Original line number Diff line number Diff line { "name": "@inject/editor", "version": "0.14.0", "description": "Editor module for Inject Exercise Platform", "version": "0.15.0", "description": "Editor module to Inject Exercise Platform", "main": "index.js", "license": "MIT", "peerDependencies": { Loading editor/src/assets/pageContent/injectSpecification.ts +10 −0 Original line number Diff line number Diff line Loading @@ -104,4 +104,14 @@ export const QUESTIONNAIRE_QUESTION_FORM: Form = { tooltip: '', optional: true, }, multiline: { label: 'Multiline', tooltip: '', optional: true, }, relatedMilestones: { label: 'Related Milestones', tooltip: '', optional: true, }, } editor/src/components/InjectSpecification/QuestionnaireForm/FreeformForm.tsx 0 → 100644 +200 −0 Original line number Diff line number Diff line import type { ButtonProps } from '@blueprintjs/core' import { Button, Dialog, DialogBody, DialogFooter, InputGroup, } from '@blueprintjs/core' import type { ZustandSetterSingle } from '@inject/shared' import { compute, computed, create, notify, ShallowGet, ShallowGetSet, } from '@inject/shared' import type { FC } from 'react' import { memo, useCallback, useEffect, useState } from 'react' import { GENERIC_CONTENT } from '../../../assets/generalContent' import { QUESTIONNAIRE_QUESTION_FORM } from '../../../assets/pageContent/injectSpecification' import { db } from '../../../indexeddb/db' import { patchQuestionnaireMilestone, updateQuestionnaireQuestion, } from '../../../indexeddb/operations' import type { QuestionnaireQuestion, QuestionnaireQuestionFreeForm, } from '../../../indexeddb/types' import TooltipLabel from '../../Tooltips/TooltipLabel' import TooltipSwitch from '../../Tooltips/TooltipSwitch' import RelatedMilestoneForm from './RelatedMilestoneForm' interface QuestionnaireQuestionFormProps { questionnaireQuestion?: QuestionnaireQuestion freeformQuestion?: QuestionnaireQuestionFreeForm injectInfoId: number buttonProps: ButtonProps } type State< S = { text: string valid: boolean multiline: boolean update: (id?: number) => Promise<void> }, > = S & ZustandSetterSingle<S> const FreeformForm: FC<QuestionnaireQuestionFormProps> = ({ questionnaireQuestion, freeformQuestion, injectInfoId, buttonProps, }) => { const [isOpen, setIsOpen] = useState(false) const state = create<State>( computed((set, get) => ({ text: '', multiline: false, ...compute(get, state => ({ valid: state.text !== '', })), set: (item, value) => set({ [item]: value }), update: async id => { const { multiline, text } = get() const relatedMilestones = ( await db.questionnaireMilestones .where({ injectInfoId }) .and(ms => ms.questionId === id || ms.questionId === undefined) .toArray() ).map(it => it.id) const newId = await updateQuestionnaireQuestion( { id, injectInfoId, type: 'free-form', text, }, { multiline, relatedMilestones, } ) await patchQuestionnaireMilestone( relatedMilestones, injectInfoId, newId ) }, })) ) useEffect(() => { state.setState({ text: questionnaireQuestion?.text, multiline: freeformQuestion?.multiline, }) }, [isOpen, state, questionnaireQuestion, freeformQuestion]) const handleUpdateButton = useCallback(async () => { try { state.getState().update(questionnaireQuestion?.id) setIsOpen(false) } catch (error) { const { text } = state.getState() notify( `Failed to update questionnaire question '${text}': ${error}`, JSON.stringify(error), { intent: 'danger', } ) } }, [questionnaireQuestion?.id, state]) return ( <> <Button {...buttonProps} onClick={() => setIsOpen(true)} /> <Dialog isOpen={isOpen} onClose={() => setIsOpen(false)} icon={questionnaireQuestion ? 'edit' : 'plus'} title={ questionnaireQuestion ? 'Edit questionnaire question' : 'New questionnaire question' } > <DialogBody> <TooltipLabel label={QUESTIONNAIRE_QUESTION_FORM.text}> <ShallowGetSet store={state} get={({ text }) => text} set={({ set }) => set} > {(text, set) => ( <InputGroup placeholder='Input text' value={text} onChange={e => set('text', e.target.value)} /> )} </ShallowGetSet> </TooltipLabel> <ShallowGetSet store={state} get={({ multiline }) => multiline} set={({ set }) => set} > {(multiline, set) => ( <TooltipSwitch label={QUESTIONNAIRE_QUESTION_FORM.multiline} switchProps={{ checked: multiline, onChange: () => set('multiline', !multiline), }} /> )} </ShallowGetSet> <RelatedMilestoneForm questionId={freeformQuestion?.id} injectInfoId={injectInfoId} /> </DialogBody> <DialogFooter actions={ <ShallowGet store={state} get={({ valid }) => valid}> {isValid => questionnaireQuestion ? ( <Button disabled={!isValid} onClick={() => handleUpdateButton()} intent='primary' icon='edit' text={GENERIC_CONTENT.buttons.save} /> ) : ( <Button disabled={!isValid} onClick={() => handleUpdateButton()} intent='primary' icon='plus' text={GENERIC_CONTENT.buttons.add} /> ) } </ShallowGet> } /> </Dialog> </> ) } export default memo(FreeformForm) editor/src/components/InjectSpecification/QuestionnaireForm/QuestionnaireQuestion.tsx +109 −15 Original line number Diff line number Diff line import { Button, ButtonGroup, Card } from '@blueprintjs/core' import { notify } from '@inject/shared' import { useLiveQuery } from 'dexie-react-hooks' import { range } from 'lodash' import type { FC } from 'react' import { memo, useCallback, useEffect, useState } from 'react' import { memo, useCallback, useMemo } from 'react' import { db } from '../../../indexeddb/db' import { deleteQuestionnaireQuestion } from '../../../indexeddb/operations' import { type QuestionnaireQuestion } from '../../../indexeddb/types' import QuestionnaireQuestionForm from './QuestionnaireQuestionForm' import FreeformForm from './FreeformForm' import RadioForm from './RadioForm' interface QuestionnaireQuestionProps { questionnaireQuestion: QuestionnaireQuestion } const QuestionnaireQuestionItem: FC<QuestionnaireQuestionProps> = ({ const QuestionnaireQuestionRadio: FC<QuestionnaireQuestionProps> = ({ questionnaireQuestion, }) => { const [answers, setAnswers] = useState<string[]>([]) const radioQuestion = useLiveQuery( () => db.questionnaireQuestionRadio.get({ id: questionnaireQuestion.id, injectInfoId: questionnaireQuestion.injectInfoId, }), [questionnaireQuestion.id] ) const answers = useMemo( () => radioQuestion?.labels ? radioQuestion?.labels.split(', ') : range(1, (radioQuestion?.max || 1) + 1).map(value => value.toString() ), [radioQuestion?.labels, radioQuestion?.max] ) const handleDeleteButton = useCallback( async (question: QuestionnaireQuestion) => { Loading @@ -33,14 +52,6 @@ const QuestionnaireQuestionItem: FC<QuestionnaireQuestionProps> = ({ [] ) useEffect(() => { setAnswers( questionnaireQuestion.labels ? questionnaireQuestion.labels.split(', ') : range(1, questionnaireQuestion.max + 1).map(value => value.toString()) ) }, [questionnaireQuestion]) return ( <Card style={{ display: 'flex', flexDirection: 'column', alignItems: 'start' }} Loading @@ -55,8 +66,9 @@ const QuestionnaireQuestionItem: FC<QuestionnaireQuestionProps> = ({ > <span style={{ flexGrow: 1 }}>{questionnaireQuestion.text}</span> <ButtonGroup> <QuestionnaireQuestionForm <RadioForm questionnaireQuestion={questionnaireQuestion} radioQuestion={radioQuestion} injectInfoId={questionnaireQuestion.injectInfoId} buttonProps={{ minimal: true, Loading @@ -76,8 +88,7 @@ const QuestionnaireQuestionItem: FC<QuestionnaireQuestionProps> = ({ <li key={i} style={{ fontWeight: questionnaireQuestion.correct === i + 1 ? 'bold' : 'normal', fontWeight: radioQuestion?.correct === i + 1 ? 'bold' : 'normal', }} > {answer} Loading @@ -88,4 +99,87 @@ const QuestionnaireQuestionItem: FC<QuestionnaireQuestionProps> = ({ ) } const QuestionnaireQuestionFreeform: FC<QuestionnaireQuestionProps> = ({ questionnaireQuestion, }) => { const freeformQuestion = useLiveQuery( () => db.questionnaireQuestionFreeform.get({ id: questionnaireQuestion.id, injectInfoId: questionnaireQuestion.injectInfoId, }), [questionnaireQuestion.id] ) const handleDeleteButton = useCallback( async (question: QuestionnaireQuestion) => { try { await deleteQuestionnaireQuestion(question.id) } catch (error) { notify( `Failed to delete question '${question.text}': ${error}`, JSON.stringify(error), { intent: 'danger', } ) } }, [] ) return ( <Card style={{ display: 'flex', flexDirection: 'column', alignItems: 'start' }} > <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%', }} > <span style={{ flexGrow: 1 }}>{questionnaireQuestion.text}</span> <ButtonGroup> <FreeformForm questionnaireQuestion={questionnaireQuestion} injectInfoId={questionnaireQuestion.injectInfoId} freeformQuestion={freeformQuestion} buttonProps={{ minimal: true, icon: 'edit', style: { marginRight: '1rem' }, }} /> <Button minimal icon='cross' onClick={() => handleDeleteButton(questionnaireQuestion)} /> </ButtonGroup> </div> </Card> ) } const QuestionnaireQuestionItem: FC<QuestionnaireQuestionProps> = ({ questionnaireQuestion, }) => { switch (questionnaireQuestion.type) { case 'radio': return ( <QuestionnaireQuestionRadio questionnaireQuestion={questionnaireQuestion} /> ) case 'free-form': return ( <QuestionnaireQuestionFreeform questionnaireQuestion={questionnaireQuestion} /> ) } } export default memo(QuestionnaireQuestionItem) editor/src/components/InjectSpecification/QuestionnaireForm/QuestionnaireQuestionForm.tsxdeleted 100644 → 0 +0 −205 Original line number Diff line number Diff line import type { ButtonProps } from '@blueprintjs/core' import { Button, Dialog, DialogBody, DialogFooter, InputGroup, } from '@blueprintjs/core' import { notify } from '@inject/shared' import type { FC } from 'react' import { memo, useCallback, useEffect, useState } from 'react' import { GENERIC_CONTENT } from '../../../assets/generalContent' import { QUESTIONNAIRE_QUESTION_FORM } from '../../../assets/pageContent/injectSpecification' import { addQuestionnaireQuestion, updateQuestionnaireQuestion, } from '../../../indexeddb/operations' import type { QuestionnaireQuestion } from '../../../indexeddb/types' import TooltipLabel from '../../Tooltips/TooltipLabel' import TooltipSwitch from '../../Tooltips/TooltipSwitch' import QuestionCustomLabels from './QuestionCustomLabels' import QuestionRangeLabels from './QuestionRangeLabels' interface QuestionnaireQuestionFormProps { questionnaireQuestion?: QuestionnaireQuestion injectInfoId: number buttonProps: ButtonProps } const QuestionnaireQuestionForm: FC<QuestionnaireQuestionFormProps> = ({ questionnaireQuestion, injectInfoId, buttonProps, }) => { const [isOpen, setIsOpen] = useState(false) const [isValid, setIsValid] = useState(false) const [text, setText] = useState<string>('') const [max, setMax] = useState<number>(1) const [correct, setCorrect] = useState<number>(0) const [customLabels, setCustomLabels] = useState<boolean>(false) const [labels, setLabels] = useState<string>('') useEffect(() => { setText(questionnaireQuestion?.text || '') setMax(questionnaireQuestion?.max || 1) setCorrect(questionnaireQuestion?.correct || 0) setCustomLabels( (questionnaireQuestion?.labels !== '' && questionnaireQuestion?.labels !== undefined) || false ) setLabels(questionnaireQuestion?.labels || '') }, [questionnaireQuestion, isOpen]) useEffect(() => { setIsValid( text !== '' && ((customLabels && labels !== '') || (!customLabels && max > 0)) ) }, [text, customLabels, labels, max]) const clearInput = useCallback(() => { setText('') setMax(1) setCorrect(0) setCustomLabels(false) setLabels('') }, []) const handleAddButton = useCallback( async (questionnaireQuestion: Omit<QuestionnaireQuestion, 'id'>) => { try { await addQuestionnaireQuestion(questionnaireQuestion) clearInput() setIsOpen(false) } catch (error) { notify( `Failed to add questionnaire question '${questionnaireQuestion.text}': ${error}`, JSON.stringify(error), { intent: 'danger', } ) } }, [] ) const handleUpdateButton = useCallback( async (questionnaireQuestion: QuestionnaireQuestion) => { try { await updateQuestionnaireQuestion(questionnaireQuestion) setIsOpen(false) } catch (error) { notify( `Failed to update questionnaire question '${questionnaireQuestion.text}': ${error}`, JSON.stringify(error), { intent: 'danger', } ) } }, [] ) const onCorrectChange = useCallback( (newCorrect: number) => { if (correct === newCorrect) { setCorrect(0) } else { setCorrect(newCorrect) } }, [correct] ) return ( <> <Button {...buttonProps} onClick={() => setIsOpen(true)} /> <Dialog isOpen={isOpen} onClose={() => setIsOpen(false)} icon={questionnaireQuestion ? 'edit' : 'plus'} title={ questionnaireQuestion ? 'Edit questionnaire question' : 'New questionnaire question' } > <DialogBody> <TooltipLabel label={QUESTIONNAIRE_QUESTION_FORM.text}> <InputGroup placeholder='Input text' value={text} onChange={e => setText(e.target.value)} /> </TooltipLabel> <TooltipSwitch label={QUESTIONNAIRE_QUESTION_FORM.customLabels} switchProps={{ checked: customLabels, onChange: () => setCustomLabels(!customLabels), }} /> {customLabels ? ( <QuestionCustomLabels labels={labels} correct={correct} onLabelsChange={(value: string) => setLabels(value)} onCorrectChange={(value: number) => onCorrectChange(value)} /> ) : ( <QuestionRangeLabels max={max} correct={correct} onMaxChange={(value: number) => setMax(value)} onCorrectChange={(value: number) => onCorrectChange(value)} /> )} </DialogBody> <DialogFooter actions={ questionnaireQuestion ? ( <Button disabled={!isValid} onClick={() => handleUpdateButton({ id: questionnaireQuestion.id, injectInfoId, text, max: customLabels ? labels.split(', ').length : max, correct, labels: customLabels ? labels : '', }) } intent='primary' icon='edit' text={GENERIC_CONTENT.buttons.save} /> ) : ( <Button disabled={!isValid} onClick={() => handleAddButton({ injectInfoId, text, max: customLabels ? labels.split(', ').length : max, correct, labels: customLabels ? labels : '', }) } intent='primary' icon='plus' text={GENERIC_CONTENT.buttons.add} /> ) } /> </Dialog> </> ) } export default memo(QuestionnaireQuestionForm) Loading
editor/package.json +2 −2 Original line number Diff line number Diff line { "name": "@inject/editor", "version": "0.14.0", "description": "Editor module for Inject Exercise Platform", "version": "0.15.0", "description": "Editor module to Inject Exercise Platform", "main": "index.js", "license": "MIT", "peerDependencies": { Loading
editor/src/assets/pageContent/injectSpecification.ts +10 −0 Original line number Diff line number Diff line Loading @@ -104,4 +104,14 @@ export const QUESTIONNAIRE_QUESTION_FORM: Form = { tooltip: '', optional: true, }, multiline: { label: 'Multiline', tooltip: '', optional: true, }, relatedMilestones: { label: 'Related Milestones', tooltip: '', optional: true, }, }
editor/src/components/InjectSpecification/QuestionnaireForm/FreeformForm.tsx 0 → 100644 +200 −0 Original line number Diff line number Diff line import type { ButtonProps } from '@blueprintjs/core' import { Button, Dialog, DialogBody, DialogFooter, InputGroup, } from '@blueprintjs/core' import type { ZustandSetterSingle } from '@inject/shared' import { compute, computed, create, notify, ShallowGet, ShallowGetSet, } from '@inject/shared' import type { FC } from 'react' import { memo, useCallback, useEffect, useState } from 'react' import { GENERIC_CONTENT } from '../../../assets/generalContent' import { QUESTIONNAIRE_QUESTION_FORM } from '../../../assets/pageContent/injectSpecification' import { db } from '../../../indexeddb/db' import { patchQuestionnaireMilestone, updateQuestionnaireQuestion, } from '../../../indexeddb/operations' import type { QuestionnaireQuestion, QuestionnaireQuestionFreeForm, } from '../../../indexeddb/types' import TooltipLabel from '../../Tooltips/TooltipLabel' import TooltipSwitch from '../../Tooltips/TooltipSwitch' import RelatedMilestoneForm from './RelatedMilestoneForm' interface QuestionnaireQuestionFormProps { questionnaireQuestion?: QuestionnaireQuestion freeformQuestion?: QuestionnaireQuestionFreeForm injectInfoId: number buttonProps: ButtonProps } type State< S = { text: string valid: boolean multiline: boolean update: (id?: number) => Promise<void> }, > = S & ZustandSetterSingle<S> const FreeformForm: FC<QuestionnaireQuestionFormProps> = ({ questionnaireQuestion, freeformQuestion, injectInfoId, buttonProps, }) => { const [isOpen, setIsOpen] = useState(false) const state = create<State>( computed((set, get) => ({ text: '', multiline: false, ...compute(get, state => ({ valid: state.text !== '', })), set: (item, value) => set({ [item]: value }), update: async id => { const { multiline, text } = get() const relatedMilestones = ( await db.questionnaireMilestones .where({ injectInfoId }) .and(ms => ms.questionId === id || ms.questionId === undefined) .toArray() ).map(it => it.id) const newId = await updateQuestionnaireQuestion( { id, injectInfoId, type: 'free-form', text, }, { multiline, relatedMilestones, } ) await patchQuestionnaireMilestone( relatedMilestones, injectInfoId, newId ) }, })) ) useEffect(() => { state.setState({ text: questionnaireQuestion?.text, multiline: freeformQuestion?.multiline, }) }, [isOpen, state, questionnaireQuestion, freeformQuestion]) const handleUpdateButton = useCallback(async () => { try { state.getState().update(questionnaireQuestion?.id) setIsOpen(false) } catch (error) { const { text } = state.getState() notify( `Failed to update questionnaire question '${text}': ${error}`, JSON.stringify(error), { intent: 'danger', } ) } }, [questionnaireQuestion?.id, state]) return ( <> <Button {...buttonProps} onClick={() => setIsOpen(true)} /> <Dialog isOpen={isOpen} onClose={() => setIsOpen(false)} icon={questionnaireQuestion ? 'edit' : 'plus'} title={ questionnaireQuestion ? 'Edit questionnaire question' : 'New questionnaire question' } > <DialogBody> <TooltipLabel label={QUESTIONNAIRE_QUESTION_FORM.text}> <ShallowGetSet store={state} get={({ text }) => text} set={({ set }) => set} > {(text, set) => ( <InputGroup placeholder='Input text' value={text} onChange={e => set('text', e.target.value)} /> )} </ShallowGetSet> </TooltipLabel> <ShallowGetSet store={state} get={({ multiline }) => multiline} set={({ set }) => set} > {(multiline, set) => ( <TooltipSwitch label={QUESTIONNAIRE_QUESTION_FORM.multiline} switchProps={{ checked: multiline, onChange: () => set('multiline', !multiline), }} /> )} </ShallowGetSet> <RelatedMilestoneForm questionId={freeformQuestion?.id} injectInfoId={injectInfoId} /> </DialogBody> <DialogFooter actions={ <ShallowGet store={state} get={({ valid }) => valid}> {isValid => questionnaireQuestion ? ( <Button disabled={!isValid} onClick={() => handleUpdateButton()} intent='primary' icon='edit' text={GENERIC_CONTENT.buttons.save} /> ) : ( <Button disabled={!isValid} onClick={() => handleUpdateButton()} intent='primary' icon='plus' text={GENERIC_CONTENT.buttons.add} /> ) } </ShallowGet> } /> </Dialog> </> ) } export default memo(FreeformForm)
editor/src/components/InjectSpecification/QuestionnaireForm/QuestionnaireQuestion.tsx +109 −15 Original line number Diff line number Diff line import { Button, ButtonGroup, Card } from '@blueprintjs/core' import { notify } from '@inject/shared' import { useLiveQuery } from 'dexie-react-hooks' import { range } from 'lodash' import type { FC } from 'react' import { memo, useCallback, useEffect, useState } from 'react' import { memo, useCallback, useMemo } from 'react' import { db } from '../../../indexeddb/db' import { deleteQuestionnaireQuestion } from '../../../indexeddb/operations' import { type QuestionnaireQuestion } from '../../../indexeddb/types' import QuestionnaireQuestionForm from './QuestionnaireQuestionForm' import FreeformForm from './FreeformForm' import RadioForm from './RadioForm' interface QuestionnaireQuestionProps { questionnaireQuestion: QuestionnaireQuestion } const QuestionnaireQuestionItem: FC<QuestionnaireQuestionProps> = ({ const QuestionnaireQuestionRadio: FC<QuestionnaireQuestionProps> = ({ questionnaireQuestion, }) => { const [answers, setAnswers] = useState<string[]>([]) const radioQuestion = useLiveQuery( () => db.questionnaireQuestionRadio.get({ id: questionnaireQuestion.id, injectInfoId: questionnaireQuestion.injectInfoId, }), [questionnaireQuestion.id] ) const answers = useMemo( () => radioQuestion?.labels ? radioQuestion?.labels.split(', ') : range(1, (radioQuestion?.max || 1) + 1).map(value => value.toString() ), [radioQuestion?.labels, radioQuestion?.max] ) const handleDeleteButton = useCallback( async (question: QuestionnaireQuestion) => { Loading @@ -33,14 +52,6 @@ const QuestionnaireQuestionItem: FC<QuestionnaireQuestionProps> = ({ [] ) useEffect(() => { setAnswers( questionnaireQuestion.labels ? questionnaireQuestion.labels.split(', ') : range(1, questionnaireQuestion.max + 1).map(value => value.toString()) ) }, [questionnaireQuestion]) return ( <Card style={{ display: 'flex', flexDirection: 'column', alignItems: 'start' }} Loading @@ -55,8 +66,9 @@ const QuestionnaireQuestionItem: FC<QuestionnaireQuestionProps> = ({ > <span style={{ flexGrow: 1 }}>{questionnaireQuestion.text}</span> <ButtonGroup> <QuestionnaireQuestionForm <RadioForm questionnaireQuestion={questionnaireQuestion} radioQuestion={radioQuestion} injectInfoId={questionnaireQuestion.injectInfoId} buttonProps={{ minimal: true, Loading @@ -76,8 +88,7 @@ const QuestionnaireQuestionItem: FC<QuestionnaireQuestionProps> = ({ <li key={i} style={{ fontWeight: questionnaireQuestion.correct === i + 1 ? 'bold' : 'normal', fontWeight: radioQuestion?.correct === i + 1 ? 'bold' : 'normal', }} > {answer} Loading @@ -88,4 +99,87 @@ const QuestionnaireQuestionItem: FC<QuestionnaireQuestionProps> = ({ ) } const QuestionnaireQuestionFreeform: FC<QuestionnaireQuestionProps> = ({ questionnaireQuestion, }) => { const freeformQuestion = useLiveQuery( () => db.questionnaireQuestionFreeform.get({ id: questionnaireQuestion.id, injectInfoId: questionnaireQuestion.injectInfoId, }), [questionnaireQuestion.id] ) const handleDeleteButton = useCallback( async (question: QuestionnaireQuestion) => { try { await deleteQuestionnaireQuestion(question.id) } catch (error) { notify( `Failed to delete question '${question.text}': ${error}`, JSON.stringify(error), { intent: 'danger', } ) } }, [] ) return ( <Card style={{ display: 'flex', flexDirection: 'column', alignItems: 'start' }} > <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%', }} > <span style={{ flexGrow: 1 }}>{questionnaireQuestion.text}</span> <ButtonGroup> <FreeformForm questionnaireQuestion={questionnaireQuestion} injectInfoId={questionnaireQuestion.injectInfoId} freeformQuestion={freeformQuestion} buttonProps={{ minimal: true, icon: 'edit', style: { marginRight: '1rem' }, }} /> <Button minimal icon='cross' onClick={() => handleDeleteButton(questionnaireQuestion)} /> </ButtonGroup> </div> </Card> ) } const QuestionnaireQuestionItem: FC<QuestionnaireQuestionProps> = ({ questionnaireQuestion, }) => { switch (questionnaireQuestion.type) { case 'radio': return ( <QuestionnaireQuestionRadio questionnaireQuestion={questionnaireQuestion} /> ) case 'free-form': return ( <QuestionnaireQuestionFreeform questionnaireQuestion={questionnaireQuestion} /> ) } } export default memo(QuestionnaireQuestionItem)
editor/src/components/InjectSpecification/QuestionnaireForm/QuestionnaireQuestionForm.tsxdeleted 100644 → 0 +0 −205 Original line number Diff line number Diff line import type { ButtonProps } from '@blueprintjs/core' import { Button, Dialog, DialogBody, DialogFooter, InputGroup, } from '@blueprintjs/core' import { notify } from '@inject/shared' import type { FC } from 'react' import { memo, useCallback, useEffect, useState } from 'react' import { GENERIC_CONTENT } from '../../../assets/generalContent' import { QUESTIONNAIRE_QUESTION_FORM } from '../../../assets/pageContent/injectSpecification' import { addQuestionnaireQuestion, updateQuestionnaireQuestion, } from '../../../indexeddb/operations' import type { QuestionnaireQuestion } from '../../../indexeddb/types' import TooltipLabel from '../../Tooltips/TooltipLabel' import TooltipSwitch from '../../Tooltips/TooltipSwitch' import QuestionCustomLabels from './QuestionCustomLabels' import QuestionRangeLabels from './QuestionRangeLabels' interface QuestionnaireQuestionFormProps { questionnaireQuestion?: QuestionnaireQuestion injectInfoId: number buttonProps: ButtonProps } const QuestionnaireQuestionForm: FC<QuestionnaireQuestionFormProps> = ({ questionnaireQuestion, injectInfoId, buttonProps, }) => { const [isOpen, setIsOpen] = useState(false) const [isValid, setIsValid] = useState(false) const [text, setText] = useState<string>('') const [max, setMax] = useState<number>(1) const [correct, setCorrect] = useState<number>(0) const [customLabels, setCustomLabels] = useState<boolean>(false) const [labels, setLabels] = useState<string>('') useEffect(() => { setText(questionnaireQuestion?.text || '') setMax(questionnaireQuestion?.max || 1) setCorrect(questionnaireQuestion?.correct || 0) setCustomLabels( (questionnaireQuestion?.labels !== '' && questionnaireQuestion?.labels !== undefined) || false ) setLabels(questionnaireQuestion?.labels || '') }, [questionnaireQuestion, isOpen]) useEffect(() => { setIsValid( text !== '' && ((customLabels && labels !== '') || (!customLabels && max > 0)) ) }, [text, customLabels, labels, max]) const clearInput = useCallback(() => { setText('') setMax(1) setCorrect(0) setCustomLabels(false) setLabels('') }, []) const handleAddButton = useCallback( async (questionnaireQuestion: Omit<QuestionnaireQuestion, 'id'>) => { try { await addQuestionnaireQuestion(questionnaireQuestion) clearInput() setIsOpen(false) } catch (error) { notify( `Failed to add questionnaire question '${questionnaireQuestion.text}': ${error}`, JSON.stringify(error), { intent: 'danger', } ) } }, [] ) const handleUpdateButton = useCallback( async (questionnaireQuestion: QuestionnaireQuestion) => { try { await updateQuestionnaireQuestion(questionnaireQuestion) setIsOpen(false) } catch (error) { notify( `Failed to update questionnaire question '${questionnaireQuestion.text}': ${error}`, JSON.stringify(error), { intent: 'danger', } ) } }, [] ) const onCorrectChange = useCallback( (newCorrect: number) => { if (correct === newCorrect) { setCorrect(0) } else { setCorrect(newCorrect) } }, [correct] ) return ( <> <Button {...buttonProps} onClick={() => setIsOpen(true)} /> <Dialog isOpen={isOpen} onClose={() => setIsOpen(false)} icon={questionnaireQuestion ? 'edit' : 'plus'} title={ questionnaireQuestion ? 'Edit questionnaire question' : 'New questionnaire question' } > <DialogBody> <TooltipLabel label={QUESTIONNAIRE_QUESTION_FORM.text}> <InputGroup placeholder='Input text' value={text} onChange={e => setText(e.target.value)} /> </TooltipLabel> <TooltipSwitch label={QUESTIONNAIRE_QUESTION_FORM.customLabels} switchProps={{ checked: customLabels, onChange: () => setCustomLabels(!customLabels), }} /> {customLabels ? ( <QuestionCustomLabels labels={labels} correct={correct} onLabelsChange={(value: string) => setLabels(value)} onCorrectChange={(value: number) => onCorrectChange(value)} /> ) : ( <QuestionRangeLabels max={max} correct={correct} onMaxChange={(value: number) => setMax(value)} onCorrectChange={(value: number) => onCorrectChange(value)} /> )} </DialogBody> <DialogFooter actions={ questionnaireQuestion ? ( <Button disabled={!isValid} onClick={() => handleUpdateButton({ id: questionnaireQuestion.id, injectInfoId, text, max: customLabels ? labels.split(', ').length : max, correct, labels: customLabels ? labels : '', }) } intent='primary' icon='edit' text={GENERIC_CONTENT.buttons.save} /> ) : ( <Button disabled={!isValid} onClick={() => handleAddButton({ injectInfoId, text, max: customLabels ? labels.split(', ').length : max, correct, labels: customLabels ? labels : '', }) } intent='primary' icon='plus' text={GENERIC_CONTENT.buttons.add} /> ) } /> </Dialog> </> ) } export default memo(QuestionnaireQuestionForm)