Skip to content

Commit

Permalink
WAITP-1452: Recent survey response page (#5157)
Browse files Browse the repository at this point in the history
* WAITP-1478 Add types definition for RecentSurveyResponseRoute

* WAITP-1478 Regen types

* WAITP-1478 Add route for RecentSurveyResponse

* WAITP-1478 Fixup export

* WAITP-1478 Update url

* WAITP-1478 Regen types

* useSurveyResponse

* survey review screen

* wip set default values

* set up route

* Update useLogin.ts

* refactor survey context to be at survey level

* remove bad merge

* survey data

* basic working setup

* remove debug changes

* split out paginator

* put page back in survey layout

* refactor layout components

* Update index.ts

* Update SurveyContext.tsx

* Update SurveyReviewSection.tsx

---------

Co-authored-by: Ethan McQuarrie <ethan@beyondessential.com.au>
  • Loading branch information
tcaiger and EMcQ-BES authored Nov 13, 2023
1 parent 3a780ec commit 67073ab
Show file tree
Hide file tree
Showing 22 changed files with 422 additions and 210 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,42 @@ export type SingleSurveyResponseRequest = Request<
DatatrakWebSingleSurveyResponseRequest.ReqQuery
>;

const ANSWER_COLUMNS = [
'text', 'question_id'
]
const ANSWER_COLUMNS = ['text', 'question_id'];

const DEFAULT_FIELDS = [
'assessor_name',
'country.name',
'data_time',
'entity.name',
'id',
'survey.name',
'survey.code',
];

export class SingleSurveyResponseRoute extends Route<SingleSurveyResponseRequest> {
public async buildResponse() {
const { ctx, params } = this.req;
const { ctx, params, query } = this.req;
const { id: responseId } = params;

const surveyResponse = await ctx.services.central.fetchResources(`surveyResponses/${responseId}`);
const { fields = DEFAULT_FIELDS } = query;

const surveyResponse = await ctx.services.central.fetchResources(
`surveyResponses/${responseId}`,
{ columns: fields },
);
const answerList = await ctx.services.central.fetchResources('answers', {
filter: { survey_response_id: surveyResponse.id },
columns: ANSWER_COLUMNS,
});
const answers = answerList.reduce((output: Record<string, string>, answer: { question_id: string, text: string }) => ({ ...output, [answer.question_id]: answer.text }), {});
const answers = answerList.reduce(
(output: Record<string, string>, answer: { question_id: string; text: string }) => ({
...output,
[answer.question_id]: answer.text,
}),
{},
);

return camelcaseKeys({ ...surveyResponse, answers }, { deep: true });
// Don't return the answers in camel case because the keys are question IDs which we want in lowercase
return camelcaseKeys({ ...surveyResponse, answers });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const DEFAULT_FIELDS = [
'entity.name',
'id',
'survey.name',
'survey.code',
];

const DEFAULT_LIMIT = 16;
Expand Down
5 changes: 4 additions & 1 deletion packages/datatrak-web-server/src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ export { EntitiesRequest, EntitiesRoute } from './EntitiesRoute';
export { ProjectRequest, ProjectRoute } from './ProjectRoute';
export { SubmitSurveyRequest, SubmitSurveyRoute } from './SubmitSurvey/SubmitSurveyRoute';
export { RecentSurveysRequest, RecentSurveysRoute } from './RecentSurveysRoute';
export { SingleSurveyResponseRequest, SingleSurveyResponseRoute } from './SingleSurveyResponseRoute';
export {
SingleSurveyResponseRequest,
SingleSurveyResponseRoute,
} from './SingleSurveyResponseRoute';
export { LeaderboardRequest, LeaderboardRoute } from './LeaderboardRoute';
export { ActivityFeedRequest, ActivityFeedRoute } from './ActivityFeedRoute';
2 changes: 2 additions & 0 deletions packages/datatrak-web/src/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
ForgotPasswordPage,
ResetPasswordPage,
AccountSettingsPage,
SurveyResponsePage,
} from './views';
import { useUser } from './api/queries';
import { ROUTES } from './constants';
Expand Down Expand Up @@ -113,6 +114,7 @@ export const SurveyPageRoutes = (
<Route index element={<SurveyStartRedirect />} />
<Route path={ROUTES.SURVEY_SUCCESS} element={<SurveySuccessScreen />} />
<Route element={<SurveyLayout />}>
<Route path={ROUTES.SURVEY_RESPONSE} element={<SurveyResponsePage />} />
<Route path={ROUTES.SURVEY_REVIEW} element={<SurveyReviewScreen />} />
<Route
path={ROUTES.SURVEY_SCREEN}
Expand Down
1 change: 1 addition & 0 deletions packages/datatrak-web/src/api/queries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export { useUser } from './useUser';
export { useProjects } from './useProjects';
export { useSurveys } from './useSurveys';
export { useSurvey } from './useSurvey';
export { useSurveyResponse } from './useSurveyResponse';
export { useSurveyResponses, useCurrentUserSurveyResponses } from './useSurveyResponses';
export { useEntities } from './useEntities';
export { useEntity } from './useEntity';
Expand Down
16 changes: 16 additions & 0 deletions packages/datatrak-web/src/api/queries/useSurveyResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Tupaia
* Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd
*/
import { useQuery } from 'react-query';
import { DatatrakWebSingleSurveyResponseRequest } from '@tupaia/types';
import { get } from '../api';

export const useSurveyResponse = (surveyResponseId?: string) => {
return useQuery(
['surveyResponse', surveyResponseId],
(): Promise<DatatrakWebSingleSurveyResponseRequest.ResBody> =>
get(`surveyResponse/${surveyResponseId}`),
{ enabled: !!surveyResponseId },
);
};
1 change: 1 addition & 0 deletions packages/datatrak-web/src/constants/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const ROUTES = {
SURVEY_SCREEN: `${SURVEY_URL}/:screenNumber`,
SURVEY_SUCCESS: `${SURVEY_URL}/success`,
SURVEY_REVIEW: `${SURVEY_URL}/review`,
SURVEY_RESPONSE: `${SURVEY_URL}/response/:surveyResponseId`,
ACCOUNT_SETTINGS: '/account-settings',
CHANGE_PROJECT: '/change-project',
VERIFY_EMAIL: '/verify-email',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Tupaia
* Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd
*/

import React from 'react';
import styled from 'styled-components';
import { useOutletContext } from 'react-router-dom';
import ArrowBackIosIcon from '@material-ui/icons/ArrowBackIos';
import { useSurveyForm } from '../SurveyContext';
import { Button } from '../../../components';
import { useIsMobile } from '../../../utils';

const FormActions = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 0.5rem;
border-top: 1px solid ${props => props.theme.palette.divider};
button:last-child {
margin-left: auto;
}
${({ theme }) => theme.breakpoints.up('md')} {
padding: 1rem;
}
`;

const ButtonGroup = styled.div`
display: flex;
button,
a {
&:last-child {
margin-left: 1rem;
}
}
`;

const BackButton = styled(Button).attrs({
startIcon: <ArrowBackIosIcon />,
variant: 'text',
color: 'default',
})`
${({ theme }) => theme.breakpoints.down('md')} {
padding-left: 0.8rem;
.MuiButton-startIcon {
margin-right: 0.25rem;
}
}
`;

type SurveyLayoutContextT = { isLoading: boolean; onStepPrevious: () => void };

export const SurveyPaginator = () => {
const { isLast, isReviewScreen, openCancelConfirmation } = useSurveyForm();
const isMobile = useIsMobile();
const { isLoading, onStepPrevious } = useOutletContext<SurveyLayoutContextT>();

const getNextButtonText = () => {
if (isReviewScreen) return 'Submit';
if (isLast) {
return isMobile ? 'Review' : 'Review and submit';
}
return 'Next';
};

const nextButtonText = getNextButtonText();

return (
<FormActions>
<BackButton onClick={onStepPrevious} disabled={isLoading}>
Back
</BackButton>
<ButtonGroup>
<Button onClick={openCancelConfirmation} variant="outlined" disabled={isLoading}>
Cancel
</Button>
<Button type="submit" disabled={isLoading}>
{nextButtonText}
</Button>
</ButtonGroup>
</FormActions>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Tupaia
* Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd
*/

import React from 'react';
import { QuestionType } from '@tupaia/types';
import styled from 'styled-components';
import { Typography } from '@material-ui/core';
import { SurveyQuestionGroup } from './SurveyQuestionGroup';
import { formatSurveyScreenQuestions, getSurveyScreenNumber } from '../utils';
import { useSurveyForm } from '../SurveyContext';

const Section = styled.section`
padding: 1rem 0;
&:first-child {
padding-top: 0;
}
`;

const SectionHeader = styled(Typography).attrs({
variant: 'h3',
})`
font-size: 1rem;
font-weight: ${({ theme }) => theme.typography.fontWeightMedium};
margin-bottom: 1rem;
${({ theme }) => theme.breakpoints.up('sm')} {
font-size: 1.125rem;
}
`;

const Fieldset = styled.fieldset.attrs({
disabled: true,
})`
border: none;
margin: 0;
padding: 0;
input,
label,
button,
.MuiInputBase-root,
link {
pointer-events: none;
}
`;
export const SurveyReviewSection = () => {
const { visibleScreens } = useSurveyForm();

if (!visibleScreens || !visibleScreens.length) {
return null;
}

// split the questions into sections by screen so it's easier to read the long form
const questionSections = visibleScreens.map(screen => {
const { surveyScreenComponents } = screen;
const screenNumber = getSurveyScreenNumber(visibleScreens, screen);
const heading = surveyScreenComponents[0].text;
const firstQuestionIsInstruction = surveyScreenComponents[0].type === QuestionType.Instruction;

// if the first question is an instruction, don't display it, because it will be displayed as the heading
const questionsToDisplay = firstQuestionIsInstruction
? surveyScreenComponents.slice(1)
: surveyScreenComponents;
return {
heading,
questions: formatSurveyScreenQuestions(questionsToDisplay, screenNumber),
};
});
return (
<Fieldset>
{questionSections.map(({ heading, questions }, index) => (
<Section key={index}>
<SectionHeader>{heading}</SectionHeader>
<SurveyQuestionGroup questions={questions} />
</Section>
))}
</Fieldset>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,9 @@ export const SurveySideMenu = () => {
setFormData,
isReviewScreen,
isSuccessScreen,
isResponseScreen,
} = useSurveyForm();
if (isReviewScreen || isSuccessScreen) return null;
if (isReviewScreen || isSuccessScreen || isResponseScreen) return null;
const onChangeScreen = () => {
setFormData(getValues());
if (isMobile) toggleSideMenu();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const CountryName = styled.span`

export const SurveyToolbar = () => {
const { surveyCode, screenNumber: screenNumberParam } = useParams();
const { screenNumber, numberOfScreens } = useSurveyForm();
const { screenNumber, numberOfScreens, isResponseScreen } = useSurveyForm();
const { data: survey } = useSurvey(surveyCode);
const { data: user } = useUser();
const isMobile = useIsMobile();
Expand All @@ -69,6 +69,10 @@ export const SurveyToolbar = () => {
};
const surveyName = getDisplaySurveyName();

if (isResponseScreen) {
return null;
}

return (
<Toolbar $toolbarIsTransparent={!screenNumberParam}>
<SurveyTitleWrapper>
Expand Down
2 changes: 2 additions & 0 deletions packages/datatrak-web/src/features/Survey/Components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ export { SurveyQuestion } from './SurveyQuestion';
export { SurveyQuestionGroup } from './SurveyQuestionGroup';
export { SurveyToolbar } from './SurveyToolbar';
export * from './SurveySideMenu';
export { SurveyReviewSection } from './SurveyReviewSection';
export { SurveyPaginator } from './SurveyPaginator';
Loading

0 comments on commit 67073ab

Please sign in to comment.