diff --git a/.unimportedrc.json b/.unimportedrc.json index 591359b..cfdef84 100644 --- a/.unimportedrc.json +++ b/.unimportedrc.json @@ -8,6 +8,7 @@ "#app/*": ["./src/App/*"], "#views/*": ["./src/views/*"], "#utils/*": ["./src/utils/*"], + "#types/*": ["./src/types/*"], "#redirects/*": ["./src/redirects/*"], "#contexts/*": ["./src/contexts/*"], "#components/*": ["./src/components/*"], diff --git a/backend b/backend index 8dfbaab..50b1881 160000 --- a/backend +++ b/backend @@ -1 +1 @@ -Subproject commit 8dfbaab8faffdc5739f7e28737625d962383c77b +Subproject commit 50b18813c4ead11777f53b426bc2b4a77fa4c0cc diff --git a/index.html b/index.html index 9e2a430..7eeeb9b 100644 --- a/index.html +++ b/index.html @@ -24,7 +24,7 @@ diff --git a/src/App/Auth.tsx b/src/App/Auth.tsx index 8aab3af..6b94a95 100644 --- a/src/App/Auth.tsx +++ b/src/App/Auth.tsx @@ -1,5 +1,6 @@ import type { ReactElement } from 'react'; import { useContext } from 'react'; +import { PendingMessage } from '@the-deep/deep-ui'; import { Navigate } from 'react-router-dom'; import { isDefined } from '@togglecorp/fujs'; import { gql, useQuery } from '@apollo/client'; @@ -40,7 +41,9 @@ function Auth(props: Props) { const { userDetails, setUser } = useContext(UserContext); - useQuery( + const { + loading, + } = useQuery( ME, { onCompleted: (response) => { @@ -52,6 +55,12 @@ function Auth(props: Props) { }, ); + if (loading) { + return ( + + ); + } + if (context.visibility === 'is-authenticated' && !userDetails) { return ( diff --git a/src/App/index.tsx b/src/App/index.tsx index e7282c7..a077a12 100644 --- a/src/App/index.tsx +++ b/src/App/index.tsx @@ -94,15 +94,11 @@ function App() { setUserDetails(undefined); }, []); - const setUser = useCallback((newUserDetails: UserDetails) => { - setUserDetails(newUserDetails); - }, []); - const userContextValue = useMemo(() => ({ userDetails, - setUser, + setUser: setUserDetails, removeUser, - }), [userDetails, setUser, removeUser]); + }), [userDetails, removeUser]); return ( diff --git a/src/components/EditQuestionnaireModal/index.tsx b/src/components/EditQuestionnaireModal/index.tsx index a7e172d..22ee026 100644 --- a/src/components/EditQuestionnaireModal/index.tsx +++ b/src/components/EditQuestionnaireModal/index.tsx @@ -2,8 +2,10 @@ import { useCallback, useMemo } from 'react'; import { isDefined, isNotDefined } from '@togglecorp/fujs'; import { gql, useMutation, useQuery } from '@apollo/client'; import { - Modal, Button, + Modal, + MultiSelectInput, + NumberInput, TextInput, useAlert, } from '@the-deep/deep-ui'; @@ -24,8 +26,13 @@ import { QuestionnaireDetailQuery, QuestionnaireDetailQueryVariables, QuestionnaireCreateInput, + QuestionnaireMetadataQuery, + QuestionnaireMetadataQueryVariables, } from '#generated/types'; -import MetaDataInputs from '#components/MetaDataInputs'; +import { + enumKeySelector, + enumLabelSelector, +} from '#utils/common'; import styles from './index.module.css'; @@ -78,12 +85,12 @@ const QUESTIONNAIRE_DETAIL = gql` title projectId createdAt - dataCollectionMethod - dataCollectionMethodDisplay - enumeratorSkill - enumeratorSkillDisplay - priorityLevelDisplay - priorityLevel + dataCollectionMethods + dataCollectionMethodsDisplay + enumeratorSkills + enumeratorSkillsDisplay + priorityLevels + priorityLevelsDisplay requiredDuration } } @@ -91,6 +98,25 @@ const QUESTIONNAIRE_DETAIL = gql` } `; +const QUESTIONNAIRE_METADATA = gql` + query QuestionnaireMetadata { + enums { + QuestionnaireEnumeratorSkills { + key + label + } + QuestionnairePriorityLevels { + key + label + } + QuestionnaireDataCollectionMethods { + key + label + } + } + } +`; + type FormType = PartialForm; type FormSchema = ObjectSchema; type FormSchemaFields = ReturnType; @@ -101,9 +127,9 @@ const schema: FormSchema = { required: true, requiredValidation: requiredStringCondition, }, - dataCollectionMethod: {}, - enumeratorSkill: {}, - priorityLevel: {}, + dataCollectionMethods: {}, + enumeratorSkills: {}, + priorityLevels: {}, requiredDuration: {}, }), }; @@ -152,7 +178,14 @@ function EditQuestionnaireModal(props: Props) { setValue, } = useForm(schema, { value: initialValue }); - const fieldError = getErrorObject(formError); + const error = getErrorObject(formError); + + const { + data: metadataOptions, + loading: metadataOptionsPending, + } = useQuery( + QUESTIONNAIRE_METADATA, + ); useQuery( QUESTIONNAIRE_DETAIL, @@ -163,9 +196,9 @@ function EditQuestionnaireModal(props: Props) { const questionnaireDetails = response?.private?.projectScope?.questionnaire; setValue({ title: questionnaireDetails?.title, - priorityLevel: questionnaireDetails?.priorityLevel, - enumeratorSkill: questionnaireDetails?.enumeratorSkill, - dataCollectionMethod: questionnaireDetails?.dataCollectionMethod, + priorityLevels: questionnaireDetails?.priorityLevels, + enumeratorSkills: questionnaireDetails?.enumeratorSkills, + dataCollectionMethods: questionnaireDetails?.dataCollectionMethods, requiredDuration: questionnaireDetails?.requiredDuration, }); }, @@ -243,6 +276,10 @@ function EditQuestionnaireModal(props: Props) { }, ); + const priorityLevelOptions = metadataOptions?.enums.QuestionnairePriorityLevels; + const skillOptions = metadataOptions?.enums.QuestionnaireEnumeratorSkills; + const collectionMethodOptions = metadataOptions?.enums.QuestionnaireDataCollectionMethods; + const handleSubmit = useCallback(() => { const handler = createSubmitHandler( validate, @@ -303,15 +340,49 @@ function EditQuestionnaireModal(props: Props) { label="Title" placeholder="Questionnaire Title" value={formValue?.title} - error={fieldError?.title} + error={error?.title} onChange={setFieldValue} autoFocus /> - - + + + ); diff --git a/src/components/MetaDataInputs/index.tsx b/src/components/MetaDataInputs/index.tsx index 24d28e0..6ed954f 100644 --- a/src/components/MetaDataInputs/index.tsx +++ b/src/components/MetaDataInputs/index.tsx @@ -23,17 +23,17 @@ import { } from '#utils/common'; const METADATA_OPTIONS = gql` - query MetaDataOptions{ + query MetaDataOptions { enums { - QuestionnaireEnumeratorSkill { + QuestionnaireEnumeratorSkills { key label } - QuestionnairePriorityLevel { + QuestionnairePriorityLevels { key label } - QuestionnaireDataCollectionMethod { + QuestionnaireDataCollectionMethods { key label } @@ -74,11 +74,11 @@ function MetaDataInputs(props: Props) { const error = getErrorObject(riskyError); - const priorityLevelOptions = metaDataOptions?.enums.QuestionnairePriorityLevel; + const priorityLevelOptions = metaDataOptions?.enums.QuestionnairePriorityLevels; - const skillOptions = metaDataOptions?.enums.QuestionnaireEnumeratorSkill; + const skillOptions = metaDataOptions?.enums.QuestionnaireEnumeratorSkills; - const collectionMethodOptions = metaDataOptions?.enums.QuestionnaireDataCollectionMethod; + const collectionMethodOptions = metaDataOptions?.enums.QuestionnaireDataCollectionMethods; return ( <> @@ -120,7 +120,7 @@ function MetaDataInputs(props: Props) { /> About -
['projectScope']>['choiceCollection']>['choices']>[number]; const rankChoiceKeySelector = (c: ChoiceType) => c.id; interface Props { className?: string; label?: string; hint?: string | null; - choiceCollectionId: string | undefined | null; - projectId: string; + choiceCollection?: ChoiceCollectionType; } function RankQuestionPreview(props: Props) { @@ -62,34 +33,10 @@ function RankQuestionPreview(props: Props) { className, label, hint, - choiceCollectionId, - projectId, + choiceCollection, } = props; - const rankChoicesVariables = useMemo(() => { - if (isNotDefined(projectId) || isNotDefined(choiceCollectionId)) { - return undefined; - } - return ({ - projectId, - choiceCollectionId, - }); - }, [ - projectId, - choiceCollectionId, - ]); - - const { - data: optionsListResponse, - } = useQuery( - RANK_CHOICES, - { - skip: isNotDefined(rankChoicesVariables), - variables: rankChoicesVariables, - }, - ); - - const choices = optionsListResponse?.private?.projectScope?.choiceCollection?.choices ?? []; + const choices = choiceCollection?.choices; const rankChoiceRendererParams = useCallback((_: string, datum: ChoiceType) => ({ title: datum.label, diff --git a/src/components/questionPreviews/SelectMultipleQuestionPreview/index.tsx b/src/components/questionPreviews/SelectMultipleQuestionPreview/index.tsx index 958a61c..3e35ee6 100644 --- a/src/components/questionPreviews/SelectMultipleQuestionPreview/index.tsx +++ b/src/components/questionPreviews/SelectMultipleQuestionPreview/index.tsx @@ -1,11 +1,9 @@ -import { useCallback, useMemo } from 'react'; +import { useCallback } from 'react'; import { MdOutlineChecklist, } from 'react-icons/md'; -import { gql, useQuery } from '@apollo/client'; import { _cs, - isNotDefined, noOp, } from '@togglecorp/fujs'; import { @@ -15,41 +13,20 @@ import { TextOutput, } from '@the-deep/deep-ui'; -import { MultipleOptionListQuery, MultipleOptionListQueryVariables } from '#generated/types'; +import { + ChoiceCollectionType, + ChoiceType, +} from '#types/common'; import styles from './index.module.css'; -const MULTIPLE_OPTION_LIST = gql` - query MultipleOptionList( - $projectId: ID!, - $choiceCollectionId: ID!, - ) { - private { - projectScope(pk: $projectId) { - choiceCollection(pk: $choiceCollectionId) { - id - label - name - choices { - id - label - name - } - } - } - } - } -`; - -type CheckboxType = NonNullable['choiceCollection']>['choices'][number]; -const checkboxKeySelector = (d: CheckboxType) => d.id; +const choiceKeySelector = (d: ChoiceType) => d.id; interface Props { className?: string; label?: string; hint?: string | null; - choiceCollectionId: string | undefined | null; - projectId: string; + choiceCollection: ChoiceCollectionType | undefined; } function SelectMultipleQuestionPreview(props: Props) { @@ -57,35 +34,10 @@ function SelectMultipleQuestionPreview(props: Props) { className, label, hint, - choiceCollectionId, - projectId, + choiceCollection, } = props; - const optionListVariables = useMemo(() => { - if (isNotDefined(projectId) || isNotDefined(choiceCollectionId)) { - return undefined; - } - return ({ - projectId, - choiceCollectionId, - }); - }, [ - projectId, - choiceCollectionId, - ]); - - const { - data: optionsListResponse, - loading: OptionsListLoading, - } = useQuery( - MULTIPLE_OPTION_LIST, - { - skip: isNotDefined(optionListVariables), - variables: optionListVariables, - }, - ); - - const checkboxListRendererParams = useCallback((_: string, datum: CheckboxType) => ({ + const checkboxRendererParams = useCallback((_: string, datum: ChoiceType) => ({ label: datum?.label, name: 'choiceCollection', value: false, @@ -109,13 +61,13 @@ function SelectMultipleQuestionPreview(props: Props) { >
diff --git a/src/components/questionPreviews/SelectOneQuestionPreview/index.tsx b/src/components/questionPreviews/SelectOneQuestionPreview/index.tsx index 9b9519d..e23cad5 100644 --- a/src/components/questionPreviews/SelectOneQuestionPreview/index.tsx +++ b/src/components/questionPreviews/SelectOneQuestionPreview/index.tsx @@ -1,13 +1,10 @@ -import { useMemo } from 'react'; import { IoRadioButtonOn, } from 'react-icons/io5'; import { _cs, - isNotDefined, noOp, } from '@togglecorp/fujs'; -import { gql, useQuery } from '@apollo/client'; import { Element, RadioInput, @@ -15,39 +12,12 @@ import { } from '@the-deep/deep-ui'; import { - SingleOptionListQuery, - SingleOptionListQueryVariables, -} from '#generated/types'; -import { - type ProjectScope, -} from '#utils/common'; + ChoiceCollectionType, + ChoiceType, +} from '#types/common'; import styles from './index.module.css'; -const SINGLE_OPTION_LIST = gql` - query SingleOptionList( - $projectId: ID!, - $choiceCollectionId: ID!, - ) { - private { - projectScope(pk: $projectId) { - choiceCollection(pk: $choiceCollectionId) { - label - id - name - choices { - id - label - name - } - } - } - } - } -`; - -type ChoiceType = NonNullable['choiceCollection']>['choices']>[number]; - const choiceCollectionKeySelector = (d: ChoiceType) => d.id; const choiceCollectionLabelSelector = (d: ChoiceType) => d.label; @@ -55,8 +25,7 @@ interface Props { className?: string; label?: string; hint?: string | null; - choiceCollectionId: string | undefined | null; - projectId: string; + choiceCollection: ChoiceCollectionType; } function SelectOneQuestionPreview(props: Props) { @@ -64,36 +33,9 @@ function SelectOneQuestionPreview(props: Props) { className, label, hint, - choiceCollectionId, - projectId, + choiceCollection, } = props; - const optionListVariables = useMemo(() => { - if (isNotDefined(projectId) || isNotDefined(choiceCollectionId)) { - return undefined; - } - return ({ - projectId, - choiceCollectionId, - }); - }, [ - projectId, - choiceCollectionId, - ]); - - const { - data: optionsListResponse, - loading: optionListLoading, - } = useQuery( - SINGLE_OPTION_LIST, - { - skip: isNotDefined(optionListVariables), - variables: optionListVariables, - }, - ); - - const optionsList = optionsListResponse?.private?.projectScope?.choiceCollection?.choices ?? []; - return (
diff --git a/src/index.css b/src/index.css index 501236d..07e0c63 100644 --- a/src/index.css +++ b/src/index.css @@ -68,6 +68,9 @@ body { background-color: var(--dui-color-foreground-scrollbar); } + h1, h2, h3, h4, h5, h6 { + text-transform: unset!important; + } } body { diff --git a/src/types/common.ts b/src/types/common.ts new file mode 100644 index 0000000..e6859cb --- /dev/null +++ b/src/types/common.ts @@ -0,0 +1,13 @@ +export interface ChoiceType { + id: string; + name: string; + label: string; + collectionId: string; +} + +export interface ChoiceCollectionType { + id: string; + name: string; + label: string; + choices: ChoiceType[]; +} diff --git a/src/views/About/IntroText/index.module.css b/src/views/About/IntroText/index.module.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/views/About/IntroText/index.tsx b/src/views/About/IntroText/index.tsx index 3c5d0f9..ae2ae13 100644 --- a/src/views/About/IntroText/index.tsx +++ b/src/views/About/IntroText/index.tsx @@ -1,8 +1,6 @@ -import styles from './index.module.css'; - function IntroText() { return ( -
+

Qber is an online platform funded by the Centre for Disease Control diff --git a/src/views/About/QuestionsPreview/index.tsx b/src/views/About/QuestionsPreview/index.tsx index 80768dc..1907153 100644 --- a/src/views/About/QuestionsPreview/index.tsx +++ b/src/views/About/QuestionsPreview/index.tsx @@ -21,6 +21,9 @@ import ImageQuestionPreview from '#components/questionPreviews/ImageQuestionPrev import FileQuestionPreview from '#components/questionPreviews/FileQuestionPreview'; import SelectOneQuestionPreview from '#components/questionPreviews/SelectOneQuestionPreview'; import SelectMultipleQuestionPreview from '#components/questionPreviews/SelectMultipleQuestionPreview'; +import { + ChoiceCollectionType, +} from '#types/common'; import styles from './index.module.css'; @@ -49,12 +52,7 @@ const QUESTIONS_FROM_BANK = gql` hint leafGroupId qbankId - choiceCollection { - id - name - label - qbankId - } + choiceCollectionId } } } @@ -66,14 +64,14 @@ type Question = NonNullable question.id; interface QuestionProps { - projectId: string; question: Question; + choiceCollection: ChoiceCollectionType | undefined; } function QuestionRenderer(props: QuestionProps) { const { - projectId, question, + choiceCollection, } = props; return ( @@ -92,13 +90,12 @@ function QuestionRenderer(props: QuestionProps) { hint={question.hint} /> )} - {(question.type === 'RANK') && isDefined(projectId) && ( + {(question.type === 'RANK') && isDefined(choiceCollection) && ( )} {(question.type === 'DATE') && ( @@ -135,22 +132,20 @@ function QuestionRenderer(props: QuestionProps) { hint={question.hint} /> )} - {(question.type === 'SELECT_ONE') && isDefined(projectId) && ( + {(question.type === 'SELECT_ONE') && isDefined(choiceCollection) && ( )} - {(question.type === 'SELECT_MULTIPLE') && isDefined(projectId) && ( + {(question.type === 'SELECT_MULTIPLE') && isDefined(choiceCollection) && ( )}

@@ -160,12 +155,14 @@ function QuestionRenderer(props: QuestionProps) { interface Props { questionBankId: string; leafGroupId: string; + choiceCollections: ChoiceCollectionType[] | undefined; } function QuestionsPreview(props: Props) { const { leafGroupId, questionBankId, + choiceCollections, } = props; const { @@ -184,7 +181,12 @@ function QuestionsPreview(props: Props) { const questions = questionsResponse?.private?.qbQuestions?.items; const questionRendererParams = useCallback((_: string, datum: Question) => ({ question: datum, - }), []); + choiceCollection: choiceCollections?.find( + (collection) => collection.id === datum.choiceCollectionId, + ), + }), [ + choiceCollections, + ]); return ( ['questionBank']>['leafGroups']>[number]; +type QuestionGroup = NonNullable['questionBank']>['leafGroups'][number]; +export type ChoiceCollectionsType = NonNullable['questionBank']>['choiceCollections']; const subPillarKeySelector = (group: QuestionGroup) => group.id; @@ -101,7 +118,10 @@ function Pillars(props: PillarsProps) { return (
{groupedList?.map((group) => ( -
+
{group[0].category1Display}
@@ -169,7 +189,10 @@ function SubDimension(props: SubDimensionProps) { {subDimension.category2Display} {sectors?.map((sector) => (sector.category3 ? ( - +
{rightPaneShown && ( -
-
setRightPaneShown(false)} - > - - - )} - heading="Questions" - headingSize="small" - /> + setRightPaneShown(false)} + > + + + )} + heading="Questions" + headingSize="small" + > {selectedLeafGroupIds?.map((leafGroupId) => ( ))} -
+ {selectedLeafGroupIds?.length === 0 && ( + + )} + )}
diff --git a/src/views/Home/QuestionnaireItem/index.module.css b/src/views/Home/QuestionnaireItem/index.module.css index e843e61..dddf1dc 100644 --- a/src/views/Home/QuestionnaireItem/index.module.css +++ b/src/views/Home/QuestionnaireItem/index.module.css @@ -16,6 +16,7 @@ gap: var(--dui-spacing-super-large); .metadatum { + width: max-content; color: var(--dui-color-text-description); font-family: var(--dui-font-family-monospace); font-size: var(--dui-font-size-medium); @@ -24,6 +25,11 @@ font-weight: var(--dui-font-weight-medium); } } + .tags { + display: flex; + flex-wrap: wrap; + gap: var(--dui-spacing-small); + } .tag { display: flex; justify-content: center; diff --git a/src/views/Home/QuestionnaireItem/index.tsx b/src/views/Home/QuestionnaireItem/index.tsx index 38fb22a..9a92cb2 100644 --- a/src/views/Home/QuestionnaireItem/index.tsx +++ b/src/views/Home/QuestionnaireItem/index.tsx @@ -47,8 +47,6 @@ import { ExportQuestionnaireMutationVariables, ExportDetailsQuery, ExportDetailsQueryVariables, - QuestionsCountQuery, - QuestionsCountQueryVariables, } from '#generated/types'; import styles from './index.module.css'; @@ -126,26 +124,6 @@ const EXPORT_DETAILS = gql` } `; -// FIXME: Get this from questionnaire query in outside scope -const QUESTIONS_COUNT = gql` - query QuestionsCount( - $projectId: ID!, - $questionnaireId: ID!, - ){ - private { - projectScope(pk: $projectId) { - questions(filters: { - questionnaire: { - pk: $questionnaireId - } - }) { - count - } - } - } - } -`; - const DOWNLOAD_ALERT_NAME = 'questionnaire-export-download'; type QuestionnaireType = NonNullable['projectScope']>['questionnaires']>['items']>[number]; @@ -261,30 +239,7 @@ function QuestionnaireItem(props: Props) { exportIdToDownload, ]); - const questionsCountVariables = useMemo(() => { - if (isNotDefined(projectId) || isNotDefined(questionnaireItem.id)) { - return undefined; - } - return ({ - projectId, - questionnaireId: questionnaireItem.id, - }); - }, [ - questionnaireItem, - projectId, - ]); - - const { - data: questionsCountResponse, - } = useQuery( - QUESTIONS_COUNT, - { - skip: isNotDefined(questionsCountVariables), - variables: questionsCountVariables, - }, - ); - - const questionsCount = questionsCountResponse?.private?.projectScope?.questions.count; + const questionsCount = questionnaireItem?.totalQuestions.visible; const { data: exportDetailsResponse, @@ -469,14 +424,21 @@ function QuestionnaireItem(props: Props) { />
- {isDefined(questionnaireItem.dataCollectionMethodDisplay) && ( + {(questionnaireItem.dataCollectionMethodsDisplay.length > 0) && ( + - {questionnaireItem.dataCollectionMethodDisplay} - + valueContainerClassName={styles.tags} + value={questionnaireItem.dataCollectionMethodsDisplay.map( + (method) => ( + + {method} + + ), )} hideLabelColon block @@ -497,14 +459,20 @@ function QuestionnaireItem(props: Props) { block /> )} - {isDefined(questionnaireItem.enumeratorSkillDisplay) && ( + {(questionnaireItem.enumeratorSkillsDisplay.length > 0) && ( - {questionnaireItem.enumeratorSkillDisplay} - + valueContainerClassName={styles.tags} + value={questionnaireItem.enumeratorSkillsDisplay.map( + (skill) => ( + + {skill} + + ), )} hideLabelColon block diff --git a/src/views/Home/index.tsx b/src/views/Home/index.tsx index 18c11a0..f8c2bad 100644 --- a/src/views/Home/index.tsx +++ b/src/views/Home/index.tsx @@ -79,11 +79,13 @@ const QUESTIONNAIRES_FOR_PROJECT = gql` modifiedAt createdAt requiredDuration - priorityLevelDisplay - enumeratorSkillDisplay - dataCollectionMethodDisplay + priorityLevelsDisplay + enumeratorSkillsDisplay + dataCollectionMethodsDisplay + totalQuestions { + visible + } } - count } } } @@ -244,7 +246,7 @@ export function Component() { className={styles.addProjectButton} name={undefined} icons={} - variant="primary" + variant="secondary" onClick={showProjectCreateModal} > Add project @@ -292,15 +294,15 @@ export function Component() { {isDefined(activeProject) && (
} onClick={showQuestionnaireModal} + variant="tertiary" > Create Questionnaire diff --git a/src/views/QuestionnaireEdit/QuestionList/LeafNode/QuestionPreview/index.tsx b/src/views/QuestionnaireEdit/QuestionList/LeafNode/QuestionPreview/index.tsx index f998502..7736739 100644 --- a/src/views/QuestionnaireEdit/QuestionList/LeafNode/QuestionPreview/index.tsx +++ b/src/views/QuestionnaireEdit/QuestionList/LeafNode/QuestionPreview/index.tsx @@ -34,6 +34,9 @@ import FileQuestionPreview from '#components/questionPreviews/FileQuestionPrevie import SelectOneQuestionPreview from '#components/questionPreviews/SelectOneQuestionPreview'; import SelectMultipleQuestionPreview from '#components/questionPreviews/SelectMultipleQuestionPreview'; import { Attributes, Listeners } from '#components/SortableList'; +import { + ChoiceCollectionType, +} from '#types/common'; import { DeleteQuestionMutation, DeleteQuestionMutationVariables, @@ -79,6 +82,7 @@ interface QuestionProps { refetchQuestionList: () => void; attributes?: Attributes; listeners?: Listeners; + choiceCollection: ChoiceCollectionType | undefined; } function QuestionPreview(props: QuestionProps) { @@ -93,6 +97,7 @@ function QuestionPreview(props: QuestionProps) { refetchQuestionList, attributes, listeners, + choiceCollection, } = props; const alert = useAlert(); @@ -226,13 +231,12 @@ function QuestionPreview(props: QuestionProps) { hint={question.hint} /> )} - {(question.type === 'RANK') && isDefined(projectId) && ( + {(question.type === 'RANK') && isDefined(choiceCollection) && ( )} {(question.type === 'DATE') && ( @@ -269,22 +273,20 @@ function QuestionPreview(props: QuestionProps) { hint={question.hint} /> )} - {(question.type === 'SELECT_ONE') && isDefined(projectId) && ( + {(question.type === 'SELECT_ONE') && isDefined(choiceCollection) && ( )} - {(question.type === 'SELECT_MULTIPLE') && isDefined(projectId) && ( + {(question.type === 'SELECT_MULTIPLE') && isDefined(choiceCollection) && ( )} {modal} diff --git a/src/views/QuestionnaireEdit/QuestionList/LeafNode/index.tsx b/src/views/QuestionnaireEdit/QuestionList/LeafNode/index.tsx index 735b593..1c3ff99 100644 --- a/src/views/QuestionnaireEdit/QuestionList/LeafNode/index.tsx +++ b/src/views/QuestionnaireEdit/QuestionList/LeafNode/index.tsx @@ -27,6 +27,9 @@ import { import { type ProjectScope, } from '#utils/common'; +import { + ChoiceCollectionType, +} from '#types/common'; import QuestionPreview from './QuestionPreview'; import { @@ -47,6 +50,7 @@ interface Props { setSelectedQuestionType: React.Dispatch>; setActiveQuestionId: React.Dispatch>; setSelectedLeafGroupId: React.Dispatch>; + choiceCollections: ChoiceCollectionType[] | undefined; } function LeafNode(props: Props) { @@ -59,6 +63,7 @@ function LeafNode(props: Props) { setSelectedQuestionType, setActiveQuestionId, setSelectedLeafGroupId, + choiceCollections, } = props; const alert = useAlert(); @@ -201,6 +206,9 @@ function LeafNode(props: Props) { onSelectedQuestionsChange: handleSelectedQuestionsChange, setSelectedLeafGroupId, refetchQuestionList: retriggerQuestionsFetch, + choiceCollection: choiceCollections?.find( + (collection) => collection.id === datum.choiceCollectionId, + ), }), [ onEditQuestionClick, projectId, @@ -209,6 +217,7 @@ function LeafNode(props: Props) { handleSelectedQuestionsChange, setSelectedLeafGroupId, retriggerQuestionsFetch, + choiceCollections, ]); return ( diff --git a/src/views/QuestionnaireEdit/QuestionList/index.tsx b/src/views/QuestionnaireEdit/QuestionList/index.tsx index 6cf735d..719a589 100644 --- a/src/views/QuestionnaireEdit/QuestionList/index.tsx +++ b/src/views/QuestionnaireEdit/QuestionList/index.tsx @@ -13,6 +13,9 @@ import { TocItem, getChildren, } from '#utils/common'; +import { + ChoiceCollectionType, +} from '#types/common'; import LeafNode from './LeafNode'; @@ -30,6 +33,7 @@ interface QuestionRendererProps { addQuestionPaneShown: boolean; selectedGroups: string[]; setSelectedLeafGroupId: React.Dispatch>; + choiceCollections: ChoiceCollectionType[] | undefined; } function QuestionListRenderer(props: QuestionRendererProps) { @@ -45,6 +49,7 @@ function QuestionListRenderer(props: QuestionRendererProps) { handleQuestionAdd, addQuestionPaneShown, setSelectedLeafGroupId, + choiceCollections, } = props; return ( @@ -89,6 +94,7 @@ function QuestionListRenderer(props: QuestionRendererProps) { setSelectedQuestionType={setSelectedQuestionType} setActiveQuestionId={setActiveQuestionId} setSelectedLeafGroupId={setSelectedLeafGroupId} + choiceCollections={choiceCollections} /> ) : ( @@ -105,6 +111,7 @@ function QuestionListRenderer(props: QuestionRendererProps) { handleQuestionAdd={handleQuestionAdd} addQuestionPaneShown={addQuestionPaneShown} setSelectedLeafGroupId={setSelectedLeafGroupId} + choiceCollections={choiceCollections} /> )} @@ -126,6 +133,7 @@ interface Props{ selectedGroups: string[]; setSelectedLeafGroupId: React.Dispatch>; className?: string; + choiceCollections: ChoiceCollectionType[] | undefined; } function QuestionList(props: Props) { @@ -142,6 +150,7 @@ function QuestionList(props: Props) { handleQuestionAdd, addQuestionPaneShown, setSelectedLeafGroupId, + choiceCollections, } = props; const questionListRendererParams = useCallback((_: string, datum: TocItem) => ({ @@ -156,6 +165,7 @@ function QuestionList(props: Props) { handleQuestionAdd, addQuestionPaneShown, setSelectedLeafGroupId, + choiceCollections, }), [ projectId, questionnaireId, @@ -167,6 +177,7 @@ function QuestionList(props: Props) { addQuestionPaneShown, handleQuestionAdd, setSelectedLeafGroupId, + choiceCollections, ]); const finalNodes = useMemo(() => ( @@ -206,6 +217,7 @@ function QuestionList(props: Props) { addQuestionPaneShown={addQuestionPaneShown} selectedGroups={selectedGroups} setSelectedLeafGroupId={setSelectedLeafGroupId} + choiceCollections={choiceCollections} /> ); } diff --git a/src/views/QuestionnaireEdit/RankQuestionForm/index.tsx b/src/views/QuestionnaireEdit/RankQuestionForm/index.tsx index 3e9f10a..e726355 100644 --- a/src/views/QuestionnaireEdit/RankQuestionForm/index.tsx +++ b/src/views/QuestionnaireEdit/RankQuestionForm/index.tsx @@ -47,10 +47,11 @@ import { QberQuestionTypeEnum, } from '#generated/types'; import PillarSelectInput from '#components/PillarSelectInput'; -import ChoiceCollectionSelectInput, { - type ChoiceCollectionType, -} from '#components/ChoiceCollectionSelectInput'; +import ChoiceCollectionSelectInput from '#components/ChoiceCollectionSelectInput'; import MetaDataInputs from '#components/MetaDataInputs'; +import { + ChoiceCollectionType, +} from '#types/common'; import { QUESTION_FRAGMENT, @@ -159,6 +160,7 @@ interface Props { questionId?: string; onSuccess: (questionId: string | undefined) => void; selectedLeafGroupId: string; + choiceCollections?: ChoiceCollectionType[]; } function RankQuestionForm(props: Props) { @@ -168,6 +170,7 @@ function RankQuestionForm(props: Props) { questionId, onSuccess, selectedLeafGroupId, + choiceCollections, } = props; const alert = useAlert(); @@ -193,11 +196,6 @@ function RankQuestionForm(props: Props) { setError, } = useForm(schema, { value: initialFormValue }); - const [ - choiceCollectionOption, - setChoiceCollectionOption, - ] = useState(); - const fieldError = getErrorObject(formError); const questionInfoVariables = useMemo(() => { @@ -229,17 +227,12 @@ function RankQuestionForm(props: Props) { hint: questionResponse?.hint, required: questionResponse?.required, requiredDuration: questionResponse?.requiredDuration, - choiceCollection: questionResponse?.choiceCollection?.id, + choiceCollection: questionResponse?.choiceCollectionId, priorityLevel: questionResponse?.priorityLevel, dataCollectionMethod: questionResponse?.dataCollectionMethod, enumeratorSkill: questionResponse?.enumeratorSkill, constraint: questionResponse?.constraint, }); - const choiceCollection = questionResponse?.choiceCollection; - const choiceCollectionOptions = isDefined(choiceCollection) - ? [choiceCollection] - : []; - setChoiceCollectionOption(choiceCollectionOptions); }, }, ); @@ -412,8 +405,7 @@ function RankQuestionForm(props: Props) { name="choiceCollection" value={formValue.choiceCollection} label="Options" - options={choiceCollectionOption} - onOptionsChange={setChoiceCollectionOption} + options={choiceCollections} onChange={setFieldValue} projectId={projectId} questionnaireId={questionnaireId} diff --git a/src/views/QuestionnaireEdit/SelectMultipleQuestionForm/index.tsx b/src/views/QuestionnaireEdit/SelectMultipleQuestionForm/index.tsx index cbb969a..92dafbe 100644 --- a/src/views/QuestionnaireEdit/SelectMultipleQuestionForm/index.tsx +++ b/src/views/QuestionnaireEdit/SelectMultipleQuestionForm/index.tsx @@ -47,10 +47,11 @@ import { QberQuestionTypeEnum, } from '#generated/types'; import PillarSelectInput from '#components/PillarSelectInput'; -import ChoiceCollectionSelectInput, { - type ChoiceCollectionType, -} from '#components/ChoiceCollectionSelectInput'; +import ChoiceCollectionSelectInput from '#components/ChoiceCollectionSelectInput'; import MetaDataInputs from '#components/MetaDataInputs'; +import { + ChoiceCollectionType, +} from '#types/common'; import { QUESTION_FRAGMENT, @@ -159,6 +160,7 @@ interface Props { questionId?: string; onSuccess: (questionId: string | undefined) => void; selectedLeafGroupId: string; + choiceCollections?: ChoiceCollectionType[]; } function SelectMultipleQuestionForm(props: Props) { @@ -168,6 +170,7 @@ function SelectMultipleQuestionForm(props: Props) { questionId, onSuccess, selectedLeafGroupId, + choiceCollections, } = props; const alert = useAlert(); @@ -193,11 +196,6 @@ function SelectMultipleQuestionForm(props: Props) { setError, } = useForm(schema, { value: initialFormValue }); - const [ - choiceCollectionOption, - setChoiceCollectionOption, - ] = useState(); - const fieldError = getErrorObject(formError); const questionInfoVariables = useMemo(() => { @@ -229,17 +227,12 @@ function SelectMultipleQuestionForm(props: Props) { hint: questionResponse?.hint, required: questionResponse?.required, requiredDuration: questionResponse?.requiredDuration, - choiceCollection: questionResponse?.choiceCollection?.id, + choiceCollection: questionResponse?.choiceCollectionId, priorityLevel: questionResponse?.priorityLevel, dataCollectionMethod: questionResponse?.dataCollectionMethod, enumeratorSkill: questionResponse?.enumeratorSkill, constraint: questionResponse?.constraint, }); - const choiceCollection = questionResponse?.choiceCollection; - const choiceCollectionOptions = isDefined(choiceCollection) - ? [choiceCollection] - : []; - setChoiceCollectionOption(choiceCollectionOptions); }, }, ); @@ -419,8 +412,7 @@ function SelectMultipleQuestionForm(props: Props) { name="choiceCollection" value={formValue.choiceCollection} label="Options" - options={choiceCollectionOption} - onOptionsChange={setChoiceCollectionOption} + options={choiceCollections} onChange={setFieldValue} projectId={projectId} questionnaireId={questionnaireId} diff --git a/src/views/QuestionnaireEdit/SelectOneQuestionForm/index.tsx b/src/views/QuestionnaireEdit/SelectOneQuestionForm/index.tsx index 2d2761c..6f12446 100644 --- a/src/views/QuestionnaireEdit/SelectOneQuestionForm/index.tsx +++ b/src/views/QuestionnaireEdit/SelectOneQuestionForm/index.tsx @@ -47,10 +47,9 @@ import { QberQuestionTypeEnum, } from '#generated/types'; import PillarSelectInput from '#components/PillarSelectInput'; -import ChoiceCollectionSelectInput, { - type ChoiceCollectionType, -} from '#components/ChoiceCollectionSelectInput'; +import ChoiceCollectionSelectInput from '#components/ChoiceCollectionSelectInput'; import MetaDataInputs from '#components/MetaDataInputs'; +import { ChoiceCollectionType } from '#types/common'; import { QUESTION_FRAGMENT, @@ -159,6 +158,7 @@ interface Props { questionId?: string; onSuccess: (questionId: string | undefined) => void; selectedLeafGroupId: string; + choiceCollections?: ChoiceCollectionType[]; } function SelectOneQuestionForm(props: Props) { @@ -168,6 +168,7 @@ function SelectOneQuestionForm(props: Props) { questionId, onSuccess, selectedLeafGroupId, + choiceCollections, } = props; const alert = useAlert(); @@ -193,11 +194,6 @@ function SelectOneQuestionForm(props: Props) { setError, } = useForm(schema, { value: initialFormValue }); - const [ - choiceCollectionOption, - setChoiceCollectionOption, - ] = useState(); - const fieldError = getErrorObject(formError); const questionInfoVariables = useMemo(() => { @@ -229,17 +225,12 @@ function SelectOneQuestionForm(props: Props) { hint: questionResponse?.hint, required: questionResponse?.required, requiredDuration: questionResponse?.requiredDuration, - choiceCollection: questionResponse?.choiceCollection?.id, + choiceCollection: questionResponse?.choiceCollectionId, priorityLevel: questionResponse?.priorityLevel, dataCollectionMethod: questionResponse?.dataCollectionMethod, enumeratorSkill: questionResponse?.enumeratorSkill, constraint: questionResponse?.constraint, }); - const choiceCollection = questionResponse?.choiceCollection; - const choiceCollectionOptions = isDefined(choiceCollection) - ? [choiceCollection] - : []; - setChoiceCollectionOption(choiceCollectionOptions); }, }, ); @@ -419,8 +410,7 @@ function SelectOneQuestionForm(props: Props) { name="choiceCollection" value={formValue.choiceCollection} label="Options" - options={choiceCollectionOption} - onOptionsChange={setChoiceCollectionOption} + options={choiceCollections} onChange={setFieldValue} projectId={projectId} questionnaireId={questionnaireId} diff --git a/src/views/QuestionnaireEdit/index.tsx b/src/views/QuestionnaireEdit/index.tsx index f931d5d..1bcc1c2 100644 --- a/src/views/QuestionnaireEdit/index.tsx +++ b/src/views/QuestionnaireEdit/index.tsx @@ -93,6 +93,7 @@ import SelectMultipleQuestionForm from './SelectMultipleQuestionForm'; import QuestionList from './QuestionList'; import { LEAF_GROUPS_FRAGMENT, + CHOICE_COLLECTION_FRAGMENT, } from './queries'; import QuestionTypeItem, { QuestionType } from './QuestionTypeItem'; @@ -102,6 +103,7 @@ export type QuestionTabType = 'general' | 'metadata'; const QUESTIONNAIRE = gql` ${LEAF_GROUPS_FRAGMENT} + ${CHOICE_COLLECTION_FRAGMENT} query Questionnaire( $projectId: ID!, $questionnaireId: ID!, @@ -119,6 +121,9 @@ const QUESTIONNAIRE = gql` leafGroups { ...LeafGroups } + choiceCollections { + ...ChoiceCollections + } } } } @@ -481,6 +486,9 @@ export function Component() { const questionnaireTitle = questionnaireResponse?.private.projectScope?.questionnaire?.title; const projectTitle = questionnaireResponse?.private.projectScope?.project.title; + const choiceCollections = questionnaireResponse?.private?.projectScope + ?.questionnaire?.choiceCollections; + const [ triggerGroupOrderChange, ] = useMutation( @@ -836,6 +844,7 @@ export function Component() { handleQuestionAdd={handleQuestionAdd} addQuestionPaneShown={addQuestionPaneShown} setSelectedLeafGroupId={setActiveLeafGroupId} + choiceCollections={choiceCollections} />
@@ -894,6 +903,7 @@ export function Component() { questionId={activeQuestionId} onSuccess={handleQuestionCreateSuccess} selectedLeafGroupId={activeLeafGroupId} + choiceCollections={choiceCollections} /> )} {(selectedQuestionType === 'SELECT_ONE') && ( @@ -903,6 +913,7 @@ export function Component() { questionId={activeQuestionId} onSuccess={handleQuestionCreateSuccess} selectedLeafGroupId={activeLeafGroupId} + choiceCollections={choiceCollections} /> )} {(selectedQuestionType === 'SELECT_MULTIPLE') && ( @@ -912,6 +923,7 @@ export function Component() { questionId={activeQuestionId} onSuccess={handleQuestionCreateSuccess} selectedLeafGroupId={activeLeafGroupId} + choiceCollections={choiceCollections} /> )} {(selectedQuestionType === 'DATE') && ( diff --git a/src/views/QuestionnaireEdit/queries.ts b/src/views/QuestionnaireEdit/queries.ts index d43c794..5bda572 100644 --- a/src/views/QuestionnaireEdit/queries.ts +++ b/src/views/QuestionnaireEdit/queries.ts @@ -17,12 +17,7 @@ export const QUESTION_FRAGMENT = gql` dataCollectionMethod enumeratorSkill constraint - choiceCollection { - id - name - label - questionnaireId - } + choiceCollectionId } `; @@ -45,6 +40,22 @@ export const LEAF_GROUPS_FRAGMENT = gql` } `; +export const CHOICE_COLLECTION_FRAGMENT = gql` + fragment ChoiceCollections on QuestionChoiceCollectionType { + id + name + label + questionnaireId + choices { + id + clientId + name + label + collectionId + } + } +`; + export const QUESTION_INFO = gql` ${QUESTION_FRAGMENT} query QuestionInfo ( diff --git a/tsconfig.json b/tsconfig.json index 1a29918..66dcb7e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "#app/*": ["./src/App/*"], "#views/*": ["./src/views/*"], "#utils/*": ["./src/utils/*"], + "#types/*": ["./src/types/*"], "#components/*": ["./src/components/*"], "#hooks/*": ["./src/hooks/*"], "#contexts/*": ["./src/contexts/*"], diff --git a/vite.config.ts b/vite.config.ts index 68ac753..d1688a0 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -23,9 +23,9 @@ export default defineConfig(({ mode }) => { eslint: { lintCommand: 'eslint ./src', }, - stylelint: { - lintCommand: 'stylelint "./src/**/*.css"', - }, + // stylelint: { + // lintCommand: 'stylelint "./src/**/*.css"', + // }, }) : undefined, reactSwc(), tsconfigPaths(),