Skip to content

Commit

Permalink
Add signature to pdf
Browse files Browse the repository at this point in the history
  • Loading branch information
jakdan99 committed Nov 26, 2024
1 parent 775b7ab commit 9a61e3b
Show file tree
Hide file tree
Showing 6 changed files with 468 additions and 39 deletions.
375 changes: 375 additions & 0 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

33 changes: 7 additions & 26 deletions src/pages/Deployment/InformedConsentCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useEffect, useState } from "react";
import { InformedConsent } from "@carp-dk/client/models/InputDataTypes";
import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined";
import { convertICToReactPdf, formatDateTime } from "@Utils/utility";
import { pdf } from "@react-pdf/renderer";
import {
DownloadButton,
LastUploadText,
Expand All @@ -24,12 +25,6 @@ import {
} from "./styles";
import LoadingSkeleton from "../LoadingSkeleton";

interface FileInfo {
data: string;
fileName: string;
fileType: string;
}

const InformedConsentCard = () => {
const { t } = useTranslation();
const { id: studyId, deploymentId } = useParams();
Expand All @@ -48,10 +43,12 @@ const InformedConsentCard = () => {
const [consents, setConsents] =
useState<{ participant: ParticipantData; consent: InformedConsent }[]>();

const downloadFile = ({ data, fileName, fileType }: FileInfo) => {
const blob = new Blob([data], { type: fileType });
const downloadPdf = async (consent: InformedConsent) => {
const blob = await pdf(
await convertICToReactPdf(JSON.parse(consent.consent)),
).toBlob();
const a = document.createElement("a");
a.download = fileName;
a.download = "informedConsent.pdf";
a.href = window.URL.createObjectURL(blob);
const clickEvt = new MouseEvent("click", {
view: window,
Expand All @@ -62,19 +59,6 @@ const InformedConsentCard = () => {
a.remove();
};

const exportToJson = (
e: React.MouseEvent<HTMLButtonElement, MouseEvent>,
participantId: string,
consent: InformedConsent,
) => {
e.preventDefault();
downloadFile({
data: JSON.stringify(consent),
fileName: `${participantId}_informedConsent.json`,
fileType: "text/json",
});
};

useEffect(() => {
if (statuses && participantData) {
const participantGroup = statuses.groups.find(
Expand Down Expand Up @@ -157,10 +141,7 @@ const InformedConsentCard = () => {
})}
</LastUploadText>
</i>
<DownloadButton
document={convertICToReactPdf(JSON.parse(consent.consent))}
fileName="informedConsent.pdf"
>
<DownloadButton onClick={() => downloadPdf(consent)}>
<FileDownloadOutlinedIcon />
<Typography variant="h6">
{t("common:export_data")}
Expand Down
6 changes: 3 additions & 3 deletions src/pages/Deployment/InformedConsentCard/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {
Accordion,
AccordionSummary,
Stack,
Button,
} from "@mui/material";
import { PDFDownloadLink } from "@react-pdf/renderer";
import { styled } from "@Utils/theme";

export const StyledAccordion = styled(Accordion)(({ expanded }) => ({
Expand Down Expand Up @@ -66,7 +66,7 @@ export const NameContainer = styled("div")({
gap: 6,
});

export const DownloadButton = styled(PDFDownloadLink)(({ theme }) => ({
export const DownloadButton = styled(Button)(({ theme }) => ({
display: "flex",
alignItems: "center",
height: "36px",
Expand All @@ -76,7 +76,7 @@ export const DownloadButton = styled(PDFDownloadLink)(({ theme }) => ({
borderColor: theme.palette.grey[700],
borderRadius: 16,
cursor: "pointer",
textDecoration: "none",
textTransform: "none",
padding: "8px 16px",
gap: 8,
}));
Expand Down
22 changes: 18 additions & 4 deletions src/pages/Participant/InformedConsent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
useGetParticipantData,
useParticipantGroupsAccountsAndStatus,
} from "@Utils/queries/participants";
import { pdf } from "@react-pdf/renderer";
import LoadingSkeleton from "../LoadingSkeleton";
import {
DownloadButton,
Expand All @@ -34,6 +35,22 @@ const InformedConsent = () => {
error: participantGroupStatusError,
} = useParticipantGroupsAccountsAndStatus(studyId);

const downloadPdf = async () => {
const blob = await pdf(
await convertICToReactPdf(JSON.parse(consent.consent)),
).toBlob();
const a = document.createElement("a");
a.download = "informedConsent.pdf";
a.href = window.URL.createObjectURL(blob);
const clickEvt = new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true,
});
a.dispatchEvent(clickEvt);
a.remove();
};

useEffect(() => {
if (!isLoading && !participantGroupStatusLoading) {
const participant = participantGroupStatus.groups
Expand Down Expand Up @@ -96,10 +113,7 @@ const InformedConsent = () => {
{consent && (
<>
<StyledDivider />
<DownloadButton
document={convertICToReactPdf(JSON.parse(consent.consent))}
fileName="informedConsent.pdf"
>
<DownloadButton onClick={() => downloadPdf()}>
<Typography variant="h6">Export</Typography>
<FileDownloadOutlinedIcon fontSize="small" />
</DownloadButton>
Expand Down
7 changes: 3 additions & 4 deletions src/pages/Participant/InformedConsent/styles.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Card, Divider, Typography } from "@mui/material";
import { PDFDownloadLink } from "@react-pdf/renderer";
import { Button, Card, Divider, Typography } from "@mui/material";
import { styled } from "@Utils/theme";

export const StyledCard = styled(Card)({
Expand Down Expand Up @@ -28,14 +27,14 @@ export const StyledDivider = styled(Divider)(({ theme }) => ({
height: 20,
}));

export const DownloadButton = styled(PDFDownloadLink)(({ theme }) => ({
export const DownloadButton = styled(Button)(({ theme }) => ({
display: "flex",
alignItems: "center",
color: theme.palette.primary.main,
backgroundColor: "transparent",
border: "none",
cursor: "pointer",
textDecoration: "none",
textTransform: "none",
gap: 4,
}));

Expand Down
64 changes: 62 additions & 2 deletions src/utils/utility.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ import RadioButtonCheckedIcon from "@mui/icons-material/RadioButtonChecked";
import SmartphoneIcon from "@mui/icons-material/Smartphone";
import TimelineRoundedIcon from "@mui/icons-material/TimelineRounded";
import WatchRoundedIcon from "@mui/icons-material/WatchRounded";
import { Document, Page, Text, StyleSheet } from "@react-pdf/renderer";
import {
Document,
Page,
Text,
StyleSheet,
Image as PdfImage,
} from "@react-pdf/renderer";

import getSerializer = kotlinx.serialization.getSerializer;
import DefaultSerializer = carpCommon.dk.cachet.carp.common.infrastructure.serialization.JSON;
Expand Down Expand Up @@ -378,6 +384,18 @@ const styles = StyleSheet.create({
fontWeight: 300,
fontFamily: "Times-Italic",
},
date: {
margin: "0 12 12 12",
fontSize: 12,
textAlign: "justify",
fontWeight: 300,
},
signatureName: {
margin: "6 12 6 12",
fontSize: 12,
textAlign: "justify",
fontWeight: 300,
},
pageNumber: {
position: "absolute",
fontSize: 12,
Expand All @@ -389,7 +407,33 @@ const styles = StyleSheet.create({
},
});

export const convertICToReactPdf = (consent: ConsentObject) => {
export const convertByteArrayToImage = async (byteArray: number[]) => {
const imageByteArray = new Uint8Array(byteArray);
const imageBlob = new Blob([imageByteArray], { type: "image/png" });
const imageBitmap = await createImageBitmap(imageBlob);

const canvas = document.createElement("canvas");
canvas.width = imageBitmap.width;
canvas.height = imageBitmap.height;

const ctx = canvas.getContext("2d");

ctx.drawImage(imageBitmap, 0, 0);

const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

const { data } = imageData;
for (let i = 0; i < data.length; i += 4) {
data[i] = 0;
data[i + 1] = 0;
data[i + 2] = 0;
}
ctx.putImageData(imageData, 0, 0);

return canvas.toDataURL("image/png");
};

export const convertICToReactPdf = async (consent: ConsentObject) => {
return (
<Document>
<Page style={styles.body}>
Expand All @@ -405,6 +449,22 @@ export const convertICToReactPdf = (consent: ConsentObject) => {
</div>
);
})}
<PdfImage
style={{ width: 150, marginLeft: 12, marginTop: 50 }}
src={await convertByteArrayToImage(
JSON.parse(consent.signature.signatureImage),
)}
/>
<Text
style={styles.signatureName}
>{`${consent.signature.firstName} ${consent.signature.lastName}`}</Text>
<Text style={styles.date}>
{formatDateTime(consent.endDate, {
year: "numeric",
month: "long",
day: "2-digit",
})}
</Text>
<Text
style={styles.pageNumber}
render={({ pageNumber, totalPages }) =>
Expand Down

0 comments on commit 9a61e3b

Please sign in to comment.