Skip to content

Commit

Permalink
Styling
Browse files Browse the repository at this point in the history
  • Loading branch information
alexd-bes committed Sep 20, 2024
1 parent 9e46baf commit 3893d17
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 8 deletions.
6 changes: 6 additions & 0 deletions packages/datatrak-web-server/src/app/createApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ import {
UserRoute,
ResubmitSurveyResponseRequest,
ResubmitSurveyResponseRoute,
ExportSurveyResponseRequest,
ExportSurveyResponseRoute,
} from '../routes';
import { attachAccessPolicy } from './middleware';
import { API_CLIENT_PERMISSIONS } from '../constants';
Expand Down Expand Up @@ -115,6 +117,10 @@ export async function createApp() {
handleWith(ResubmitSurveyResponseRoute),
)
.post<GenerateLoginTokenRequest>('generateLoginToken', handleWith(GenerateLoginTokenRoute))
.post<ExportSurveyResponseRequest>(
'export/:surveyResponseId',
handleWith(ExportSurveyResponseRoute),
)
// Forward auth requests to web-config
.use('signup', forwardRequest(WEB_CONFIG_API_URL, { authHandlerProvider }))
.use('resendEmail', forwardRequest(WEB_CONFIG_API_URL, { authHandlerProvider }))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Tupaia
* Copyright (c) 2017 - 2024 Beyond Essential Systems Pty Ltd
*
*/

import { Request } from 'express';
import { Route } from '@tupaia/server-boilerplate';
import { downloadPageAsPDF } from '@tupaia/server-utils';

export type ExportSurveyResponseRequest = Request<
{
surveyResponseId: string;
},
{
contents: Buffer;
type: string;
},
{
baseUrl: string;
cookieDomain: string;
},
Record<string, unknown>
>;

export class ExportSurveyResponseRoute extends Route<ExportSurveyResponseRequest> {
protected type = 'download' as const;

public async buildResponse() {
const { surveyResponseId } = this.req.params;
const { baseUrl, cookieDomain } = this.req.body;
const { cookie } = this.req.headers;

if (!cookie) {
throw new Error(`Must have a valid session to export a dashboard`);
}

const pdfPageUrl = `${baseUrl}/export/${surveyResponseId}`;
const buffer = await downloadPageAsPDF(pdfPageUrl, cookie, cookieDomain, false, true);

return {
contents: buffer,
type: 'application/pdf',
};
}
}
4 changes: 4 additions & 0 deletions packages/datatrak-web-server/src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,7 @@ export {
PermissionGroupUsersRequest,
PermissionGroupUsersRoute,
} from './PermissionGroupUsersRoute';
export {
ExportSurveyResponseRequest,
ExportSurveyResponseRoute,
} from './ExportSurveyResponseRoute';
1 change: 1 addition & 0 deletions packages/datatrak-web/src/api/mutations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ export * from './useExportSurveyResponses';
export { useTupaiaRedirect } from './useTupaiaRedirect';
export { useCreateTask } from './useCreateTask';
export { useCreateTaskComment } from './useCreateTaskComment';
export { useExportSurveyResponse } from './useExportSurveyResponse';
32 changes: 32 additions & 0 deletions packages/datatrak-web/src/api/mutations/useExportSurveyResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Tupaia
* Copyright (c) 2017 - 2024 Beyond Essential Systems Pty Ltd
*/
import { useMutation } from '@tanstack/react-query';
import { API_URL, post } from '../api';
import download from 'downloadjs';

// Requests a survey response PDF export from the server, and returns the response
export const useExportSurveyResponse = (surveyResponseId: string) => {
return useMutation<any, Error, unknown, unknown>(
() => {
const baseUrl = `${window.location.protocol}//${window.location.host}`;

// Auth cookies are saved against this domain. Pass this to server, so that when it pretends to be us, it can do the same.
const cookieDomain = new URL(API_URL).hostname;

return post(`export/${surveyResponseId}`, {
responseType: 'blob',
data: {
cookieDomain,
baseUrl,
},
});
},
{
onSuccess: data => {
download(data, `survey_response_${surveyResponseId}.pdf`);
},
},
);
};
16 changes: 13 additions & 3 deletions packages/datatrak-web/src/features/SurveyResponseModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import React from 'react';
import { useForm, FormProvider } from 'react-hook-form';
import { useSearchParams } from 'react-router-dom';
import styled from 'styled-components';
import { Dialog, Typography } from '@material-ui/core';
import { CircularProgress, Dialog, Typography } from '@material-ui/core';
import {
ModalContentProvider,
ModalFooter,
Expand All @@ -20,6 +20,7 @@ import { Button, DownloadIcon, SurveyTickIcon } from '../components';
import { displayDate } from '../utils';
import { SurveyReviewSection, useSurveyResponseWithForm } from './Survey';
import { SurveyContext } from '.';
import { useExportSurveyResponse } from '../api';

const Header = styled.div`
display: flex;
Expand Down Expand Up @@ -70,9 +71,13 @@ const DownloadButton = styled(Button).attrs({
})`
margin-left: auto;
.MuiSvgIcon-root {
margin-right: 0.5rem;
margin-inline-end: 0.5rem;
font-size: 1rem;
}
.MuiCircularProgress-root {
margin-inline-start: 0.5rem;
color: ${({ theme }) => theme.palette.text.secondary};
}
`;

const getSubHeadingText = surveyResponse => {
Expand Down Expand Up @@ -101,6 +106,10 @@ const SurveyResponseModalContent = ({
const { surveyLoading } = useSurveyResponseWithForm(surveyResponse);
const subHeading = getSubHeadingText(surveyResponse);
const showLoading = isLoading || surveyLoading;
const [urlSearchParams] = useSearchParams();
const surveyResponseId = urlSearchParams.get('responseId');
const { mutate: downloadSurveyResponse, isLoading: isDownloadingSurveyResponse } =
useExportSurveyResponse(surveyResponseId!);

return (
<>
Expand All @@ -112,9 +121,10 @@ const SurveyResponseModalContent = ({
<Heading>{surveyResponse?.surveyName}</Heading>
<SubHeading>{subHeading}</SubHeading>
</div>
<DownloadButton>
<DownloadButton onClick={downloadSurveyResponse} disabled={isDownloadingSurveyResponse}>
<DownloadIcon />
Download
{isDownloadingSurveyResponse && <CircularProgress size={15} />}
</DownloadButton>
</Header>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import { getIsQuestionVisible } from '../../features/Survey/SurveyContext/utils'

const Page = styled(A4Page)`
background-color: white;
padding-block-start: 0;
padding-block-end: 1cm;
padding-inline: 1.55cm;
width: 21cm; // A4 width in cm
`;

const Header = styled.div`
Expand All @@ -28,8 +32,7 @@ const Header = styled.div`

const ScreenWrapper = styled.div`
& + & {
margin-block-start: 2rem;
padding-block-start: 2rem;
margin-block-start: 1.5rem;
}
`;

Expand All @@ -50,8 +53,9 @@ const SurveyResponseDetails = styled(Typography)`
`;

const ProjectLogo = styled.img`
height: 4rem;
max-height: 4rem;
width: auto;
max-width: 5rem;
`;

export const ExportSurveyResponsePage = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ import { Typography } from '@material-ui/core';

const QuestionWrapper = styled.div`
border-bottom: 1px solid #000;
page-break-inside: avoid;
& + & {
margin-block-start: 1.125rem;
}
`;

const InstructionQuestionText = styled(Typography)`
font-size: 0.875rem;
margin-block-start: 2rem;
margin-block-start: 1rem;
margin-block-end: 0.25rem;
font-weight: ${({ theme }) => theme.typography.fontWeightMedium};
`;
Expand Down
28 changes: 28 additions & 0 deletions packages/server-utils/src/downloadPageAsPDF.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,27 @@ const buildParams = (pdfPageUrl: string, userCookie: string, cookieDomain: strin
return { verifiedPDFPageUrl, cookies: finalisedCookieObjects };
};

const pageNumberHTML = `
<div
style="
text-align: right;
width: 13.4cm;
font-size: 6px;
margin-left: 1.2cm;
font-family: Arial, Helvetica, sans-serif;
color: #c1c1c1;
border-top: 1px solid #888888;
padding-top: 1mm;
"
>
<span>
<span class="pageNumber"></span> of <span class="totalPages"></span>
</span>
</div>
`;

/**
* @param pdfPageUrl the url to visit and download as a pdf
* @param userCookie the user's cookie to bypass auth, and ensure page renders under the correct user context
Expand All @@ -43,6 +64,7 @@ export const downloadPageAsPDF = async (
userCookie = '',
cookieDomain: string | undefined,
landscape = false,
includePageNumber = false,
) => {
let browser;
let buffer;
Expand All @@ -57,6 +79,12 @@ export const downloadPageAsPDF = async (
format: 'a4',
printBackground: true,
landscape,
displayHeaderFooter: includePageNumber,
// remove the default header so that only the page number is displayed, not a header
headerTemplate: `<div></div>`,
footerTemplate: pageNumberHTML,
//add a margin so the page number doesn't overlap with the content, and the top margin is set for overflow content
margin: includePageNumber ? { bottom: '20mm', top: '10mm' } : undefined,
});
} catch (e) {
throw new Error(`puppeteer error: ${(e as Error).message}`);
Expand Down

0 comments on commit 3893d17

Please sign in to comment.