Skip to content

Commit

Permalink
feat(datatrak): RN-1314: Auto populate entity question (#5793)
Browse files Browse the repository at this point in the history
* set primary entity

* survey response

* Update SurveyResponsePage.tsx
  • Loading branch information
tcaiger authored Jul 18, 2024
1 parent f0eb8bf commit 5ffb3e2
Show file tree
Hide file tree
Showing 9 changed files with 63 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const DEFAULT_FIELDS = [
'country.name',
'data_time',
'entity.name',
'entity.id',
'id',
'survey.name',
'survey.code',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
* Copyright (c) 2017 - 2024 Beyond Essential Systems Pty Ltd
*/

import React, { createContext, Dispatch, useContext, useEffect, useReducer } from 'react';
import React, { createContext, Dispatch, useContext, useReducer, useState } from 'react';
import { useMatch, useParams } from 'react-router-dom';
import { QuestionType } from '@tupaia/types';
import { ROUTES } from '../../../constants';
import { SurveyParams } from '../../../types';
import { useSurvey } from '../../../api';
Expand All @@ -17,6 +18,7 @@ import {
} from './utils';
import { SurveyFormContextType, surveyReducer } from './reducer';
import { ACTION_TYPES, SurveyFormAction } from './actions';
import { usePrimaryEntityLocation } from '../../../utils';

const defaultContext = {
startTime: new Date().toISOString(),
Expand All @@ -31,13 +33,16 @@ const defaultContext = {
displayQuestions: [],
sideMenuOpen: false,
cancelModalOpen: false,
primaryEntityQuestion: null,
} as SurveyFormContextType;

const SurveyFormContext = createContext(defaultContext);

export const SurveyFormDispatchContext = createContext<Dispatch<SurveyFormAction> | null>(null);

export const SurveyContext = ({ children }) => {
const [prevSurveyCode, setPrevSurveyCode] = useState<string | null>(null);
const primaryEntity = usePrimaryEntityLocation();
const [state, dispatch] = useReducer(surveyReducer, defaultContext);
const { surveyCode, ...params } = useParams<SurveyParams>();
const screenNumber = params.screenNumber ? parseInt(params.screenNumber!, 10) : null;
Expand All @@ -62,27 +67,36 @@ export const SurveyContext = ({ children }) => {
.filter(screen => screen.surveyScreenComponents.length > 0);

const activeScreen = visibleScreens?.[screenNumber! - 1]?.surveyScreenComponents || [];
const primaryEntityQuestion = flattenedScreenComponents.find(
question => question.type === QuestionType.PrimaryEntity,
);

useEffect(() => {
const initialiseFormData = () => {
if (!surveyCode || isResponseScreen) return;
// if we are on the response screen, we don't want to initialise the form data, because we want to show the user's saved answers
const initialFormData = generateCodeForCodeGeneratorQuestions(
flattenedScreenComponents,
formData,
);
dispatch({ type: ACTION_TYPES.SET_FORM_DATA, payload: initialFormData });
// update the start time when a survey is started, so that it can be passed on when submitting the survey

const currentDate = new Date();
dispatch({
type: ACTION_TYPES.SET_SURVEY_START_TIME,
payload: currentDate.toISOString(),
});
};
const initialiseFormData = () => {
if (!surveyCode || isResponseScreen) return;
// if we are on the response screen, we don't want to initialise the form data, because we want to show the user's saved answers
let initialFormData = generateCodeForCodeGeneratorQuestions(
flattenedScreenComponents,
formData,
);

if (primaryEntity && primaryEntityQuestion) {
initialFormData[primaryEntityQuestion.id as string] = primaryEntity;
}
dispatch({ type: ACTION_TYPES.SET_FORM_DATA, payload: initialFormData });
// update the start time when a survey is started, so that it can be passed on when submitting the survey

const currentDate = new Date();
dispatch({
type: ACTION_TYPES.SET_SURVEY_START_TIME,
payload: currentDate.toISOString(),
});
};

// @see https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes
if (surveyCode !== prevSurveyCode) {
setPrevSurveyCode(surveyCode as string);
initialiseFormData();
}, [surveyCode]);
}

const displayQuestions = getDisplayQuestions(activeScreen, flattenedScreenComponents);
const screenHeader = activeScreen?.[0]?.text;
Expand All @@ -100,6 +114,7 @@ export const SurveyContext = ({ children }) => {
screenHeader,
screenDetail,
visibleScreens,
primaryEntityQuestion,
}}
>
<SurveyFormDispatchContext.Provider value={dispatch}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type SurveyFormContextType = {
surveyStartTime?: string;
isSuccessScreen?: boolean;
cancelModalOpen: boolean;
primaryEntityQuestion?: SurveyScreenComponent | null;
};

export const surveyReducer = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export const ActionButton = ({ task }: ActionButtonProps) => {
variant="contained"
state={{
from: location.pathname,
primaryEntity: entity.id,
}}
>
Complete task
Expand Down
1 change: 1 addition & 0 deletions packages/datatrak-web/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export {
getTaskFilterSetting,
removeTaskFilterSetting,
} from './taskFilterSettings';
export { usePrimaryEntityLocation } from './usePrimaryEntityLocation';
18 changes: 18 additions & 0 deletions packages/datatrak-web/src/utils/usePrimaryEntityLocation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Tupaia
* Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd
*/

import { useLocation } from 'react-router-dom';

function hasPrimaryEntity(state: unknown): state is { primaryEntity: string } {
if (state !== null && typeof state === 'object' && 'primaryEntity' in state) {
return typeof state.primaryEntity === 'string';
}
return false;
}

export function usePrimaryEntityLocation() {
const location = useLocation();
return hasPrimaryEntity(location.state) ? location.state.primaryEntity : undefined;
}
6 changes: 5 additions & 1 deletion packages/datatrak-web/src/views/SurveyResponsePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,11 @@ const getSubHeadingText = surveyResponse => {

export const SurveyResponsePage = () => {
const { surveyResponseId } = useParams();
const { setFormData } = useSurveyForm();
const { setFormData, primaryEntityQuestion } = useSurveyForm();
const formContext = useFormContext();
const { data: surveyResponse } = useSurveyResponse(surveyResponseId);
const answers = surveyResponse?.answers || {};
const primaryEntityId = surveyResponse?.entityId;
const subHeading = getSubHeadingText(surveyResponse);

useEffect(() => {
Expand All @@ -94,6 +95,9 @@ export const SurveyResponsePage = () => {
const isStringifiedObject = typeof value === 'string' && value.startsWith('{');
return { ...acc, [key]: isStringifiedObject ? JSON.parse(value) : value };
}, {});
if (primaryEntityQuestion) {
formattedAnswers[primaryEntityQuestion.id as string] = primaryEntityId;
}
formContext.reset(formattedAnswers);
setFormData(formattedAnswers);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface ResBody extends KeysToCamelCase<SurveyResponse> {
answers: Record<string, string>;
countryName: Country['name'];
entityName: Entity['name'];
entityId: Entity['id'];
surveyName: Survey['name'];
surveyCode: Survey['code'];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export type SurveyScreenComponent = CamelCasedComponent &
label?: BaseSurveyScreenComponent['question_label'];
options?: Option[] | null;
screenId?: string;
id?: string;
};

type CamelCasedSurveyScreen = KeysToCamelCase<Pick<BaseSurveyScreen, 'id' | 'screen_number'>>;
Expand Down

0 comments on commit 5ffb3e2

Please sign in to comment.