applied downloadable checkbox on submit of new appeal

This commit is contained in:
2026-03-16 13:58:02 +00:00
parent 72eafd0a8e
commit 7c84344d5d
9 changed files with 189 additions and 57 deletions
+3 -1
View File
@@ -361,7 +361,9 @@ export const createAppealPDFBlob = async (
console.log("blobName:", blobName); console.log("blobName:", blobName);
const blockBlobClient = containerClient.getBlockBlobClient(blobName); const blockBlobClient = containerClient.getBlockBlobClient(blobName);
const uploadBlobResponse = await blockBlobClient.uploadStream(content); const uploadBlobResponse = Buffer.isBuffer(content)
? await blockBlobClient.uploadData(content)
: await blockBlobClient.uploadStream(content);
console.log(uploadBlobResponse); console.log(uploadBlobResponse);
let whichLocation = "846040000"; // Default value let whichLocation = "846040000"; // Default value
+8 -3
View File
@@ -265,20 +265,25 @@ export const generateAppealPDF = async (
containerID, containerID,
casefolderID, casefolderID,
appealType, appealType,
fileList fileList,
options = {}
) => { ) => {
Object.assign(formValues, { Object.assign(formValues, {
"filesList": fileList "filesList": fileList
}); });
var queryUrl = "/api/file/generateappealpdf?appealType=" + appealType; var queryUrl =
"/api/file/generateappealpdf?appealType=" +
appealType +
(options.download ? "&download=true" : "");
const hashedUrl = await buildHashedQueryUrl(queryUrl); const hashedUrl = await buildHashedQueryUrl(queryUrl);
const config = { const config = {
method: "post", method: "post",
url: hashedUrl, url: hashedUrl,
data: formValues data: formValues,
...(options.download ? { responseType: "blob" } : {})
}; };
try { try {
@@ -292,7 +292,8 @@ const RepCompleteSubmit = (props) => {
const pdfBlob = await generateRepPDF( const pdfBlob = await generateRepPDF(
repFormData, repFormData,
containerID, containerID,
caseRef caseRef,
{ download: true }
); );
if (pdfBlob) { if (pdfBlob) {
+87 -1
View File
@@ -124,6 +124,8 @@ let BuildCheckSection = (props) => {
const [confirmSections, setConfirmSections] = useState(false); const [confirmSections, setConfirmSections] = useState(false);
const [finaliseAppealProcess, setFinaliseAppealProcess] = useState(false); const [finaliseAppealProcess, setFinaliseAppealProcess] = useState(false);
const [downloadAppealForm, setDownloadAppealForm] = useState(false);
const [downloadInProgress, setDownloadInProgress] = useState(false);
const FieldsTranslations = (label) => { const FieldsTranslations = (label) => {
const router = useRouter(); const router = useRouter();
@@ -183,6 +185,65 @@ let BuildCheckSection = (props) => {
: setConfirmSections(false); : setConfirmSections(false);
}; };
const triggerAppealDownload = async () => {
if (downloadInProgress) {
return;
}
try {
setDownloadInProgress(true);
const uniqueArray = pdfObj.filesList.filter(
(value, index, self) =>
index === self.findIndex((t) => t.name === value.name)
);
pdfObj.filesList = uniqueArray;
console.log(pdfObj);
const containerID =
props.accountDetails?.containerID ||
props.props?.accountDetails?.containerID ||
props.props?.props?.accountDetails?.containerID;
const caseRef =
props?.currentView?.caseReference?.ticketnumber ||
props?.appealType?.caseReference?.ticketnumber;
if (!containerID || !caseRef) {
console.error(
"Appeal Form download skipped: missing containerID or case reference",
{ containerID, caseRef }
);
return;
}
const pdfBlob = await generateAppealPDF(
pdfObj,
containerID,
caseRef,
router.query.appealtypes,
pdfObj.filesList,
{ download: true }
);
if (pdfBlob) {
const blobUrl = window.URL.createObjectURL(pdfBlob);
const anchor = document.createElement("a");
anchor.href = blobUrl;
anchor.download = `${"appealForm"}.pdf`;
document.body.appendChild(anchor);
anchor.click();
anchor.remove();
window.URL.revokeObjectURL(blobUrl);
}
} catch (error) {
console.error("Appeal Form download failed", error);
} finally {
setDownloadInProgress(false);
}
};
let ShowDocLinks = getDocLink(docsOffline); let ShowDocLinks = getDocLink(docsOffline);
return ( return (
@@ -280,6 +341,30 @@ let BuildCheckSection = (props) => {
<> <>
<div className="govuk-form-group "> <div className="govuk-form-group ">
<fieldset className="govuk-fieldset"> <fieldset className="govuk-fieldset">
<div className="govuk-checkboxes govuk-checkboxes--small">
<div className="govuk-checkboxes__item">
<input
name="pinswg_downloadAppealForm"
id="pinswg_downloadAppealForm"
type="checkbox"
className="govuk-checkboxes__input"
checked={downloadAppealForm}
onChange={(e) => {
setDownloadAppealForm(
e.target.checked
);
}}
/>
<label
className="govuk-label govuk-checkboxes__label"
htmlFor="pinswg_downloadAppealForm"
>
{t(
"newappeal:new-appeal-check-download-label"
)}
</label>
</div>
</div>
<div className="govuk-checkboxes govuk-checkboxes--small"> <div className="govuk-checkboxes govuk-checkboxes--small">
<div className="govuk-checkboxes__item"> <div className="govuk-checkboxes__item">
<input <input
@@ -363,7 +448,8 @@ let BuildCheckSection = (props) => {
} }
className="govuk-button" className="govuk-button"
data-module="govuk-button" data-module="govuk-button"
onClick={() => { onClick={async () => {
await triggerAppealDownload();
finaliseAppeal(); finaliseAppeal();
}} }}
> >
+48 -47
View File
@@ -4,7 +4,7 @@ import xpath from "xpath";
import { import {
bytesToSize, bytesToSize,
getPickListLabel, getPickListLabel,
getDocumentTypeFromFilename, getDocumentTypeFromFilename
} from "../../components/utils"; } from "../../components/utils";
import fieldLookup from "../../data/crmfieldlookuptranslations.json"; import fieldLookup from "../../data/crmfieldlookuptranslations.json";
@@ -22,7 +22,7 @@ export const AppealRow_pdf = (props) => {
sectionCount, sectionCount,
onSubmit, onSubmit,
updateCurrentSection, updateCurrentSection,
mandatoryFieldsData, mandatoryFieldsData
} = props; } = props;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@@ -30,23 +30,23 @@ export const AppealRow_pdf = (props) => {
backgroundColor: "white", backgroundColor: "white",
padding: 20, padding: 20,
fontSize: 16, fontSize: 16,
paddingBottom: 50, paddingBottom: 50
}, },
header: { header: {
fontSize: 25, fontSize: 25,
fontFamily: "Helvetica", fontFamily: "Helvetica",
flexDirection: "row", flexDirection: "row",
justifyContent: "space-between", justifyContent: "space-between"
}, },
subHeader: { subHeader: {
fontSize: 20, fontSize: 20
}, },
logoImage: { width: "200pt" }, logoImage: { width: "200pt" },
questionBullet: { width: "5%", fontSize: 12 }, questionBullet: { width: "5%", fontSize: 12 },
questionText: { questionText: {
width: "35%", width: "35%",
fontSize: 12, fontSize: 12,
marginRight: 5, marginRight: 5
}, },
questionTextMid: { width: "80%", fontSize: 12 }, questionTextMid: { width: "80%", fontSize: 12 },
questionTextWide: { width: "95%", fontSize: 12, marginBottom: 5 }, questionTextWide: { width: "95%", fontSize: 12, marginBottom: 5 },
@@ -60,7 +60,7 @@ export const AppealRow_pdf = (props) => {
paddingLeft: 10, paddingLeft: 10,
backgroundColor: "white", backgroundColor: "white",
display: "flex", display: "flex",
flexWrap: "wrap", flexWrap: "wrap"
}, },
questionAnswerMid: { questionAnswerMid: {
color: "#757575", color: "#757575",
@@ -72,7 +72,7 @@ export const AppealRow_pdf = (props) => {
paddingLeft: 10, paddingLeft: 10,
backgroundColor: "white", backgroundColor: "white",
display: "flex", display: "flex",
flexWrap: "wrap", flexWrap: "wrap"
}, },
questionAnswerWide: { questionAnswerWide: {
color: "#757575", color: "#757575",
@@ -81,26 +81,26 @@ export const AppealRow_pdf = (props) => {
border: 1, border: 1,
borderColor: "#eee", borderColor: "#eee",
backgroundColor: "white", backgroundColor: "white",
padding: 5, padding: 5
}, },
questionAnswerRow: { questionAnswerRow: {
width: "100%", width: "100%",
flex: 1, flex: 1,
flexDirection: "row", flexDirection: "row",
flexGrow: 1, flexGrow: 1
}, },
questionAnswerColLeft: { questionAnswerColLeft: {
width: "40%", width: "40%"
}, },
questionAnswerColRight: { questionAnswerColRight: {
padding: 5, padding: 5,
width: "40%", width: "40%"
}, },
sectionContainer: { sectionContainer: {
backgroundColor: "#F8F8F8", backgroundColor: "#F8F8F8",
padding: 10, padding: 10,
marginTop: 20, marginTop: 20,
fontSize: 15, fontSize: 15
}, },
pageNumber: { pageNumber: {
position: "absolute", position: "absolute",
@@ -109,15 +109,15 @@ export const AppealRow_pdf = (props) => {
left: 0, left: 0,
right: 0, right: 0,
textAlign: "center", textAlign: "center",
color: "grey", color: "grey"
}, },
documentList: { documentList: {
fontSize: 10, fontSize: 10
}, },
richArea: { richArea: {
fontSize: 12, fontSize: 12,
flexDirection: "column", flexDirection: "column"
}, }
}); });
const parser = new DOMParser(); const parser = new DOMParser();
@@ -151,7 +151,7 @@ export const AppealRow_pdf = (props) => {
let formObj = jsonpath({ let formObj = jsonpath({
path: "$['" + appealtypes + "']", path: "$['" + appealtypes + "']",
json: fieldLookup, json: fieldLookup,
eval: true, eval: true
}); });
let labelTrans = label; let labelTrans = label;
@@ -211,7 +211,7 @@ export const AppealRow_pdf = (props) => {
<View <View
style={{ style={{
flexDirection: "row", flexDirection: "row",
marginBottom: 10, marginBottom: 10
}} }}
wrap={false} wrap={false}
> >
@@ -243,7 +243,7 @@ export const AppealRow_pdf = (props) => {
<View <View
style={{ style={{
flexDirection: "column", flexDirection: "column",
marginBottom: 10, marginBottom: 10
}} }}
wrap={false} wrap={false}
> >
@@ -251,7 +251,7 @@ export const AppealRow_pdf = (props) => {
wrap={false} wrap={false}
style={{ style={{
flexDirection: "row", flexDirection: "row",
marginBottom: 10, marginBottom: 10
}} }}
> >
<Text <Text
@@ -301,11 +301,13 @@ export const AppealRow_pdf = (props) => {
? fieldCopy.split("|")[1] ? fieldCopy.split("|")[1]
: fieldCopy; : fieldCopy;
const transData = require("../../locales/en/" + const transData = require(
(typeof fieldCopy != "undefined" "../../locales/en/" +
? fieldCopy.split(":")[0] (typeof fieldCopy != "undefined"
: "common") + ? fieldCopy.split(":")[0]
".json"); : "common") +
".json"
);
let transCopy = let transCopy =
typeof fieldCopy != "undefined" typeof fieldCopy != "undefined"
@@ -316,7 +318,7 @@ export const AppealRow_pdf = (props) => {
<View <View
style={{ style={{
flexDirection: "column", flexDirection: "column",
marginBottom: 10, marginBottom: 10
}} }}
wrap={false} wrap={false}
> >
@@ -324,7 +326,7 @@ export const AppealRow_pdf = (props) => {
wrap={false} wrap={false}
style={{ style={{
flexDirection: "row", flexDirection: "row",
marginBottom: 10, marginBottom: 10
}} }}
> >
<Text <Text
@@ -350,14 +352,14 @@ export const AppealRow_pdf = (props) => {
<View <View
style={{ style={{
flexDirection: "column", flexDirection: "column",
marginBottom: 10, marginBottom: 10
}} }}
> >
<View <View
wrap={false} wrap={false}
style={{ style={{
flexDirection: "row", flexDirection: "row",
marginBottom: 10, marginBottom: 10
}} }}
> >
<Text <Text
@@ -391,7 +393,7 @@ export const AppealRow_pdf = (props) => {
wrap={false} wrap={false}
style={{ style={{
flexDirection: "row", flexDirection: "row",
marginBottom: 10, marginBottom: 10
}} }}
> >
<Text style={styles.questionText}> <Text style={styles.questionText}>
@@ -420,16 +422,15 @@ export const AppealRow_pdf = (props) => {
//const blobList = props.filesList || []; //const blobList = props.filesList || [];
const blobList = Array.isArray( const blobList =
props.filesList?.value props.filesList.hasOwnProperty("value")
) ? props.filesList.value
? props.filesList.value : props.filesList || [];
: [];
return ( return (
<View <View
style={{ style={{
flexDirection: "column", flexDirection: "column",
marginBottom: 10, marginBottom: 10
}} }}
wrap={false} wrap={false}
> >
@@ -437,7 +438,7 @@ export const AppealRow_pdf = (props) => {
wrap={false} wrap={false}
style={{ style={{
flexDirection: "row", flexDirection: "row",
marginBottom: 10, marginBottom: 10
}} }}
> >
<Text <Text
@@ -489,7 +490,7 @@ export const AppealRow_pdf = (props) => {
<View <View
style={{ style={{
flexDirection: "column", flexDirection: "column",
marginBottom: 10, marginBottom: 10
}} }}
wrap={false} wrap={false}
> >
@@ -497,7 +498,7 @@ export const AppealRow_pdf = (props) => {
wrap={false} wrap={false}
style={{ style={{
flexDirection: "row", flexDirection: "row",
marginBottom: 10, marginBottom: 10
}} }}
> >
<Text <Text
@@ -516,7 +517,7 @@ export const AppealRow_pdf = (props) => {
<View <View
style={{ style={{
flexDirection: "column", flexDirection: "column",
marginBottom: 10, marginBottom: 10
}} }}
> >
{(props.props.hasOwnProperty( {(props.props.hasOwnProperty(
@@ -549,14 +550,14 @@ export const AppealRow_pdf = (props) => {
style={{ style={{
flexDirection: flexDirection:
"row", "row",
marginBottom: 10, marginBottom: 10
}} }}
> >
<Text <Text
style={{ style={{
width: "50%", width: "50%",
paddingRight: paddingRight:
"20", "20"
}} }}
> >
{tenant?.pinswg_agriculturaltenantname_firstname && {tenant?.pinswg_agriculturaltenantname_firstname &&
@@ -579,7 +580,7 @@ export const AppealRow_pdf = (props) => {
<Text <Text
style={{ style={{
width: "40%", width: "40%"
}} }}
> >
Notice Notice
@@ -604,7 +605,7 @@ export const AppealRow_pdf = (props) => {
<View <View
style={{ style={{
flexDirection: "column", flexDirection: "column",
marginBottom: 10, marginBottom: 10
}} }}
wrap={false} wrap={false}
> >
@@ -612,7 +613,7 @@ export const AppealRow_pdf = (props) => {
wrap={false} wrap={false}
style={{ style={{
flexDirection: "row", flexDirection: "row",
marginBottom: 10, marginBottom: 10
}} }}
> >
<Text <Text
@@ -642,7 +643,7 @@ export const AppealRow_pdf = (props) => {
wrap={false} wrap={false}
style={{ style={{
flexDirection: "row", flexDirection: "row",
marginBottom: 10, marginBottom: 10
}} }}
> >
<Text style={styles.questionText}> <Text style={styles.questionText}>
+1
View File
@@ -99,6 +99,7 @@
"new-appeal-check-confirm-paragraph-one": "Rwyn deall y gallwch ddefnyddior wybodaeth a roddais at ddibenion swyddogol mewn cysylltiad â Deddf Cynllunio Gwlad a Thref 1990 a gall manylion gan gynnwys fy enw, disgrifiad or safle a'm datganiad achos ymddangos ar-lein. Trwy gyflwyno'r ffurflen hon, rwy'n cytuno i'r wybodaeth a roddaf gael ei defnyddio yn y modd hwn.", "new-appeal-check-confirm-paragraph-one": "Rwyn deall y gallwch ddefnyddior wybodaeth a roddais at ddibenion swyddogol mewn cysylltiad â Deddf Cynllunio Gwlad a Thref 1990 a gall manylion gan gynnwys fy enw, disgrifiad or safle a'm datganiad achos ymddangos ar-lein. Trwy gyflwyno'r ffurflen hon, rwy'n cytuno i'r wybodaeth a roddaf gael ei defnyddio yn y modd hwn.",
"new-appeal-check-confirm-paragraph-two": "Mae'r data personol a ddarparwyd gennych ar y ffurflen hon yn cael ei chasglu a'i phrosesu wedi hynny yn unol â thelerau ein cofrestriad o dan Ddeddf Diogelu Data 1998. Mae rhagor o wybodaeth am y Polisi Diogelu Data ar gael yn ", "new-appeal-check-confirm-paragraph-two": "Mae'r data personol a ddarparwyd gennych ar y ffurflen hon yn cael ei chasglu a'i phrosesu wedi hynny yn unol â thelerau ein cofrestriad o dan Ddeddf Diogelu Data 1998. Mae rhagor o wybodaeth am y Polisi Diogelu Data ar gael yn ",
"new-appeal-check-confirm-paragraph-two-link": "https://www.llyw.cymru/hysbysiad-preifatrwydd-llywodraeth-cymru", "new-appeal-check-confirm-paragraph-two-link": "https://www.llyw.cymru/hysbysiad-preifatrwydd-llywodraeth-cymru",
"new-appeal-check-download-label": "Hoffwn lawrlwytho copi o'r ffurflen ar gyfer fy nghofnodion neu i'w hanfon ymlaen at yr Awdurdod Cynllunio Lleol.",
"new-appeal-complete-heading-1": "Cyflwynwyd yr apêl wedi'i gwblhau", "new-appeal-complete-heading-1": "Cyflwynwyd yr apêl wedi'i gwblhau",
"new-appeal-complete-paragraph-1": "Rydym wedi anfon e-bost cadarnhau atoch.", "new-appeal-complete-paragraph-1": "Rydym wedi anfon e-bost cadarnhau atoch.",
"new-appeal-complete-heading-2": "Beth fydd yn digwydd nesaf", "new-appeal-complete-heading-2": "Beth fydd yn digwydd nesaf",
+1
View File
@@ -99,6 +99,7 @@
"new-appeal-check-confirm-paragraph-one": "I understand that you may use the information I have given for official purposes in connection with the Town and Country Planning Act 1990 and details including my name, the site description and my statement of case may appear online. By submitting this form I am agreeing to the use of the information I provide in this way.", "new-appeal-check-confirm-paragraph-one": "I understand that you may use the information I have given for official purposes in connection with the Town and Country Planning Act 1990 and details including my name, the site description and my statement of case may appear online. By submitting this form I am agreeing to the use of the information I provide in this way.",
"new-appeal-check-confirm-paragraph-two": "The gathering and subsequent processing of the personal data supplied by you in this form, is in accordance with the terms of our registration under the Data Protection Act 1998. Further information about Data Protection Policy can be found at", "new-appeal-check-confirm-paragraph-two": "The gathering and subsequent processing of the personal data supplied by you in this form, is in accordance with the terms of our registration under the Data Protection Act 1998. Further information about Data Protection Policy can be found at",
"new-appeal-check-confirm-paragraph-two-link": "https://gov.wales/welsh-government-privacy-notice", "new-appeal-check-confirm-paragraph-two-link": "https://gov.wales/welsh-government-privacy-notice",
"new-appeal-check-download-label": "I wish to download a copy of the form for my records or to forward to the Local Planning Authority.",
"new-appeal-complete-heading-1": "Appeal Submission complete", "new-appeal-complete-heading-1": "Appeal Submission complete",
"new-appeal-complete-paragraph-1": "We have sent you a confirmation email.", "new-appeal-complete-paragraph-1": "We have sent you a confirmation email.",
"new-appeal-complete-heading-2": "What happens next", "new-appeal-complete-heading-2": "What happens next",
+8 -2
View File
@@ -11,7 +11,13 @@ ApiProxy.get(async (req, res) => {
return res.status(401).json(); return res.status(401).json();
} }
const queryPath = req.query.path; const rawQueryPath = req.query.path;
const queryPath =
typeof rawQueryPath === "string"
? rawQueryPath.split("?")[0]
: rawQueryPath;
const allowedPrefix = [ const allowedPrefix = [
"/api/endpoint/getportallogin_api", "/api/endpoint/getportallogin_api",
"/api/endpoint/deletemyrepresentations_api", "/api/endpoint/deletemyrepresentations_api",
@@ -36,7 +42,7 @@ ApiProxy.get(async (req, res) => {
return res.status(400).json(); return res.status(400).json();
} }
return res.status(200).json({ hash: hashAPIPath(queryPath) }); return res.status(200).json({ hash: hashAPIPath(rawQueryPath) });
}); });
export default ApiProxy; export default ApiProxy;
+31 -2
View File
@@ -113,6 +113,7 @@ import { other_pdf } from "../../../components/pdftemplates/other_pdf";
export default async function handler(req, res) { export default async function handler(req, res) {
var checkHash = req.query.hash; var checkHash = req.query.hash;
var appealType = req.query.appealType; var appealType = req.query.appealType;
var isDownload = req.query?.download === "true";
let appealBodyObj = let appealBodyObj =
typeof req.body === "string" ? JSON.parse(req.body) : req.body; typeof req.body === "string" ? JSON.parse(req.body) : req.body;
@@ -134,7 +135,11 @@ export default async function handler(req, res) {
return res.status(400).json(); return res.status(400).json();
} }
checkquerypath = checkquerypath + "?appealType=" + appealType; checkquerypath =
checkquerypath +
"?appealType=" +
appealType +
(isDownload ? "&download=true" : "");
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) { if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
return res.status(400).json(); return res.status(400).json();
@@ -196,6 +201,19 @@ export default async function handler(req, res) {
// ReactPDF.renderToStream(); // ReactPDF.renderToStream();
const streamToBuffer = async (readableStream) => {
return new Promise((resolve, reject) => {
const chunks = [];
readableStream.on("data", (chunk) => {
chunks.push(
Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
);
});
readableStream.on("end", () => resolve(Buffer.concat(chunks)));
readableStream.on("error", reject);
});
};
const renderedPDF = await ReactPDF.renderToStream( const renderedPDF = await ReactPDF.renderToStream(
<MyDocument docProps={blobProgress} pickListData={pickListData} /> <MyDocument docProps={blobProgress} pickListData={pickListData} />
); );
@@ -214,11 +232,22 @@ export default async function handler(req, res) {
("0" + day).slice(-2) + ("0" + day).slice(-2) +
"_-_Appeal_Form"; "_-_Appeal_Form";
const renderedPDFBuffer = await streamToBuffer(renderedPDF);
await createAppealPDFBlob( await createAppealPDFBlob(
renderedPDF, renderedPDFBuffer,
containerID, containerID,
blobProgress.pinswg_name || blobProgress.caseObj.ticketnumber blobProgress.pinswg_name || blobProgress.caseObj.ticketnumber
).then((data) => { ).then((data) => {
if (isDownload) {
const filename = `${pdfFileName}.pdf`;
res.setHeader("Content-Type", "application/pdf");
res.setHeader(
"Content-Disposition",
`attachment; filename="${filename}"`
);
return res.status(200).send(renderedPDFBuffer);
}
return res.status(200).json({ return res.status(200).json({
status: "success", status: "success",
data: data, data: data,