chekc box to download questionnaire

This commit is contained in:
2026-03-09 12:13:24 +00:00
parent e2c79d6b0f
commit 2c6d110d9b
7 changed files with 375 additions and 226 deletions
+3 -1
View File
@@ -411,7 +411,9 @@ export const createRepPDFBlob = async (
console.log("blobName:", 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);
let whichLocation =
repName.indexOf("_IP_") > 0
+146 -140
View File
File diff suppressed because it is too large Load Diff
+78 -56
View File
@@ -1,10 +1,9 @@
import { JSONPath as jsonpath } from "jsonpath-plus";
import useTranslation from "next-translate/useTranslation";
import { useRouter } from "next/router";
import { useState } from "react";
import { useDropzone } from "react-dropzone";
import { useEffect, useState } from "react";
import { connect } from "react-redux";
import { formValueSelector, reduxForm } from "redux-form";
import { reduxForm } from "redux-form";
import { useSession } from "next-auth/react";
import { generateRepPDF, sendEmail, uploadRepFiles } from "../../../actions";
@@ -33,9 +32,7 @@ import RepresentationProgress_s78 from "./representationProgress/representationP
let MakeRepresentation = (props) => {
let { t } = useTranslation();
const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
const [clearRepForm, setClearRepForm] = useState(false);
const [savingStatus, setSavingStatus] = useState(false);
const [finaliseAppealProcess, setFinaliseAppealProcess] = useState(false);
@@ -97,7 +94,7 @@ let MakeRepresentation = (props) => {
let questionnaireCount = updateQuestionnaireCount();
const isLPA =
accountDetails[
accountDetails?.[
"pinswg_typeofinvolvement@OData.Community.Display.V1.FormattedValue"
] == "LPA"
? true
@@ -164,7 +161,39 @@ let MakeRepresentation = (props) => {
return values;
};
isLPA && setRepresentationCapacity("LPA");
useEffect(() => {
if (isLPA && !currentView?.representationCapacity) {
setRepresentationCapacity("LPA");
}
}, [isLPA, currentView?.representationCapacity, setRepresentationCapacity]);
const normalizeCapacity = (rawCapacity) => {
const normalized = (rawCapacity || "")
.toString()
.toLowerCase()
.replace(/(\band|[\s\-\+\(\)\,\&])/g, "");
const capacityMap = {
appellant: "appellant",
apelydd: "appellant",
agent: "agent",
asiant: "agent",
interestedparty: "interestedparty",
lpa: "lpa",
landowner: "landowner",
[t("myrepresentations:capacity-options-arr-appellant")
.toLowerCase()
.replace(/(\band|[\s\-\+\(\)\,\&])/g, "")]: "appellant",
[t("myrepresentations:capacity-options-arr-agent")
.toLowerCase()
.replace(/(\band|[\s\-\+\(\)\,\&])/g, "")]: "agent",
[t("myrepresentations:capacity-options-arr-interested")
.toLowerCase()
.replace(/(\band|[\s\-\+\(\)\,\&])/g, "")]: "interestedparty"
};
return capacityMap[normalized] || normalized || "false";
};
const getCasePath = () => {
switch (currentType) {
@@ -257,6 +286,9 @@ let MakeRepresentation = (props) => {
})[0];
}
const safeDetailsObj = detailsObj || {};
const safeResultsObj = resultsObj || {};
const buildRepsArr = (detailsObj) => {
const buildArr = [];
const todaysDate = new Date();
@@ -440,15 +472,10 @@ let MakeRepresentation = (props) => {
);
}
const capacity = getNormalizedCapacity();
const capacity = normalizeCapacity(getNormalizedCapacity());
switch (capacity) {
case "appellant":
case "apelydd":
case "apelydd":
case t(
"myrepresentations:capacity-options-arr-appellant"
).toLowerCase(): {
case "appellant": {
const Component =
appealType === 846040002
? ConsultationAppellant
@@ -463,7 +490,6 @@ let MakeRepresentation = (props) => {
);
}
case "lpa":
case "lpa": {
return (
<RepLPA
@@ -480,12 +506,7 @@ let MakeRepresentation = (props) => {
);
}
case "agent":
case "asiant":
case "asiant":
case t(
"myrepresentations:capacity-options-arr-agent"
).toLowerCase(): {
case "agent": {
const Component =
appealType === 846040002 ? ConsultationAgent : RepAgent;
return (
@@ -498,10 +519,7 @@ let MakeRepresentation = (props) => {
);
}
case "interestedparty":
case t(
"myrepresentations:capacity-options-arr-interested"
).toLowerCase(): {
case "interestedparty": {
const Component =
appealType === 846040002
? ConsultationInterestedPartyPerson
@@ -642,7 +660,7 @@ let MakeRepresentation = (props) => {
pinswg_siteaddressline2,
pinswg_siteaddresstown,
pinswg_siteaddresspostcode
} = detailsObj;
} = safeDetailsObj;
return [
pinswg_siteaddressline1,
pinswg_siteaddressline2,
@@ -671,26 +689,28 @@ let MakeRepresentation = (props) => {
caseReferenceObj.repDetails.incidentid,
caseRef: currentRef,
casereference: currentRef,
pinswg_questionnaireduedate: detailsObj.pinswg_questionnaireduedate,
pinswg_questionnaireduedate:
safeDetailsObj.pinswg_questionnaireduedate,
pinswg_statementduedate:
detailsObj.pinswg_statementduedate ||
detailsObj.pinswg_endofrepresentationperiod,
safeDetailsObj.pinswg_statementduedate ||
safeDetailsObj.pinswg_endofrepresentationperiod,
pinswg_statementsduedate:
detailsObj.pinswg_statementsduedate ||
detailsObj.pinswg_endofrepresentationperiod,
safeDetailsObj.pinswg_statementsduedate ||
safeDetailsObj.pinswg_endofrepresentationperiod,
pinswg_finalcommentsduedate:
detailsObj.pinswg_finalcommentsduedate ||
detailsObj.pinswg_endofrepresentationperiod,
safeDetailsObj.pinswg_finalcommentsduedate ||
safeDetailsObj.pinswg_endofrepresentationperiod,
pinswg_name: currentRef,
firstname: accountDetails.firstname,
lastname: accountDetails.lastname,
emailAddress: accountDetails.emailaddress1,
siteAddress: getSiteAddress(),
pinswg_siteaddressline1: detailsObj.pinswg_siteaddressline1,
pinswg_siteaddressline2: detailsObj.pinswg_siteaddressline2,
pinswg_siteaddresstown: detailsObj.pinswg_siteaddresstown,
pinswg_siteaddresspostcode: detailsObj.pinswg_siteaddresspostcode,
pinswg_lpareference: resultsObj?.pinswg_lpareference,
pinswg_siteaddressline1: safeDetailsObj.pinswg_siteaddressline1,
pinswg_siteaddressline2: safeDetailsObj.pinswg_siteaddressline2,
pinswg_siteaddresstown: safeDetailsObj.pinswg_siteaddresstown,
pinswg_siteaddresspostcode:
safeDetailsObj.pinswg_siteaddresspostcode,
pinswg_lpareference: safeResultsObj?.pinswg_lpareference,
representationCapacity: getRepresentationCapacity(),
locale: router.locale
};
@@ -698,19 +718,19 @@ let MakeRepresentation = (props) => {
const formattedValues = {
...values,
...baseBody,
_pinswg_appellant_value: detailsObj._pinswg_appellant_value,
_pinswg_appellant_value: safeDetailsObj._pinswg_appellant_value,
"_pinswg_appellant_value@OData.Community.Display.V1.FormattedValue":
detailsObj[
safeDetailsObj[
"_pinswg_appellant_value@OData.Community.Display.V1.FormattedValue"
],
_pinswg_localplanningauthority_value:
detailsObj._pinswg_localplanningauthority_value ||
detailsObj._pinswg_associatedlpa_value,
safeDetailsObj._pinswg_localplanningauthority_value ||
safeDetailsObj._pinswg_associatedlpa_value,
"_pinswg_localplanningauthority_value@OData.Community.Display.V1.FormattedValue":
detailsObj[
safeDetailsObj[
"_pinswg_localplanningauthority_value@OData.Community.Display.V1.FormattedValue"
] ||
detailsObj[
safeDetailsObj[
"_pinswg_associatedlpa_value@OData.Community.Display.V1.FormattedValue"
]
};
@@ -769,7 +789,9 @@ let MakeRepresentation = (props) => {
uploadRepresentationFiles(filesWithLinks);
};
const isSubmitted = currentView.representationSubmit === "true";
const isSubmitted =
currentView.representationSubmit === true ||
currentView.representationSubmit === "true";
if (isSubmitted) {
sendEmailIfNeeded();
@@ -787,6 +809,10 @@ let MakeRepresentation = (props) => {
const onHandleSubmit = (values) => {
values = setRepFileName(values);
if (!caseReferenceObj?.currentReference) {
return;
}
// Determine case data source
const getCaseDetailsSource = () => {
if (router.query?.state === "edit") {
@@ -905,9 +931,7 @@ let MakeRepresentation = (props) => {
if (hasNoCapacity) {
const capacity = isLPA
? "LPA"
: values.representationCapacity
.replace(/(\band|[\s\-\+\(\)\,\&])/g, "")
.toLowerCase();
: normalizeCapacity(values.representationCapacity);
setRepresentationCapacity(capacity);
} else {
if (repType === "Questionnaire") {
@@ -1038,9 +1062,9 @@ let MakeRepresentation = (props) => {
{t("myrepresentations:statements-due-label")}:{" "}
<span className="govuk-body-s">
{formatDates(
detailsObj.pinswg_statementduedate ||
detailsObj.pinswg_statementsduedate ||
detailsObj.pinswg_endofrepresentationperiod
safeDetailsObj.pinswg_statementduedate ||
safeDetailsObj.pinswg_statementsduedate ||
safeDetailsObj.pinswg_endofrepresentationperiod
)}
</span>
</div>
@@ -1052,7 +1076,7 @@ let MakeRepresentation = (props) => {
<div className="govuk-body-s">
{t("myrepresentations:marine-impact-report")}:{" "}
<span className="govuk-body-s">
{formatDates(detailsObj.pinswg_consultationclose)}
{formatDates(safeDetailsObj.pinswg_consultationclose)}
</span>
</div>
);
@@ -1062,7 +1086,7 @@ let MakeRepresentation = (props) => {
<div className="govuk-body-s">
{t("myrepresentations:consultation-close-label")}:{" "}
<span className="govuk-body-s">
{formatDates(detailsObj.pinswg_consultationclose)}
{formatDates(safeDetailsObj.pinswg_consultationclose)}
</span>
</div>
);
@@ -1075,7 +1099,7 @@ let MakeRepresentation = (props) => {
<div className="govuk-body-s">
{t("myrepresentations:final-comments-due-label")}:{" "}
<span className="govuk-body-s">
{formatDates(detailsObj.pinswg_finalcommentsduedate)}
{formatDates(safeDetailsObj.pinswg_finalcommentsduedate)}
</span>
</div>
);
@@ -1207,8 +1231,6 @@ const mapDispatchToProps = (dispatch) => {
};
};
const selector = formValueSelector("representationForm");
export default connect(
mapStateToProps,
mapDispatchToProps
@@ -4,6 +4,7 @@ import { useRouter } from "next/router";
import { useState } from "react";
import { useDropzone } from "react-dropzone";
import { connect } from "react-redux";
import { generateRepPDF } from "../../../actions";
import RepComplete from "./representationComplete";
import RepLPAQuestionnaireCheck_enforcement from "./representationQuestionnairesCheck/representationLPAQuestionnaireCheck_enforcement";
import RepLPAQuestionnaireCheck_s78 from "./representationQuestionnairesCheck/representationLPAQuestionnaireCheck_s78";
@@ -13,17 +14,19 @@ import {
getThumbnailIconByExtension,
formatDates,
updateLinks,
getDocLink,
getDocLink
} from "../../utils";
import {
setCurrentReference,
setCurrentView,
setCurrentView
} from "../../../store/currentView/action";
const RepCompleteSubmit = (props) => {
let { t } = useTranslation();
const [confirmSections, setConfirmSections] = useState(false);
const [downloadQuestionnaire, setDownloadQuestionnaire] = useState(false);
const [downloadInProgress, setDownloadInProgress] = useState(false);
const router = useRouter();
const { locale } = router;
@@ -42,7 +45,7 @@ const RepCompleteSubmit = (props) => {
questionnaireCount,
finaliseAppealProcess,
setCurrentReference,
docsOffline,
docsOffline
} = props;
let ShowDocLinks = getDocLink(docsOffline);
@@ -169,7 +172,7 @@ const RepCompleteSubmit = (props) => {
typeof filesUploadObj[key].name != "undefined" &&
buildAppealFilesArray.push({
"name": filesUploadObj[key].name,
"size": filesUploadObj[key].contentLength,
"size": filesUploadObj[key].contentLength
});
}
@@ -185,7 +188,7 @@ const RepCompleteSubmit = (props) => {
// : [];
let arrUniq = [
...new Map(buildAppealFilesArray.map((v) => [v.name, v])).values(),
...new Map(buildAppealFilesArray.map((v) => [v.name, v])).values()
];
buildAppealFilesArray = buildAppealFilesArray.filter(function (el) {
@@ -217,7 +220,7 @@ const RepCompleteSubmit = (props) => {
arrUniq = removeEmptyObjects(arrUniq);
Object.assign(formObj.representationForm.values, {
"filesList": arrUniq,
"filesList": arrUniq
});
let filterFiles = [];
@@ -235,7 +238,6 @@ const RepCompleteSubmit = (props) => {
}
const checkRepDatePassed = () => {
return false;
if (repFormData.representationType == "Questionnaire") {
return false;
} else {
@@ -255,6 +257,62 @@ const RepCompleteSubmit = (props) => {
}
};
const triggerQuestionnaireDownload = async () => {
if (
repFormData.representationType !== "Questionnaire" ||
!downloadQuestionnaire
) {
return;
}
if (downloadInProgress) {
return;
}
try {
setDownloadInProgress(true);
const containerID =
props.accountDetails?.containerID ||
props.props?.accountDetails?.containerID ||
props.props?.props?.accountDetails?.containerID;
const caseRef =
currentView?.caseReference?.ticketnumber ||
repFormData?.caseRef ||
repFormData?.ticketnumber;
if (!containerID || !caseRef) {
console.error(
"Questionnaire download skipped: missing containerID or case reference",
{ containerID, caseRef }
);
return;
}
const pdfBlob = await generateRepPDF(
repFormData,
containerID,
caseRef,
{ download: true }
);
if (pdfBlob) {
const blobUrl = window.URL.createObjectURL(pdfBlob);
const anchor = document.createElement("a");
anchor.href = blobUrl;
anchor.download = `${repFormData.repfile_name || "questionnaire"}.pdf`;
document.body.appendChild(anchor);
anchor.click();
anchor.remove();
window.URL.revokeObjectURL(blobUrl);
}
} catch (error) {
console.error("Questionnaire download failed", error);
} finally {
setDownloadInProgress(false);
}
};
return (
<>
{currentView.representationSubmitConfirmation == true ? (
@@ -305,12 +363,12 @@ const RepCompleteSubmit = (props) => {
"Questionnaire"
? "Holiadur"
: repFormData.representationType ==
"Statement"
? "Datganiad"
: repFormData.representationType ==
"Final comments"
? "Sylwadau terfynol"
: repFormData.representationType
"Statement"
? "Datganiad"
: repFormData.representationType ==
"Final comments"
? "Sylwadau terfynol"
: repFormData.representationType
: repFormData.representationType}
</dd>
@@ -368,7 +426,7 @@ const RepCompleteSubmit = (props) => {
"appealType":
repFormData.appealType,
"repDetails":
repFormData,
repFormData
});
setRepresentationSubmit(
false
@@ -421,7 +479,7 @@ const RepCompleteSubmit = (props) => {
"appealType":
repFormData.appealType,
"repDetails":
repFormData,
repFormData
}
);
setRepresentationSubmit(
@@ -458,7 +516,7 @@ const RepCompleteSubmit = (props) => {
<span
className="multiline-elipsis"
dangerouslySetInnerHTML={{
__html: repFormData.representationComments,
__html: repFormData.representationComments
}}
/>
</dd>
@@ -482,7 +540,7 @@ const RepCompleteSubmit = (props) => {
"appealType":
repFormData.appealType,
"repDetails":
repFormData,
repFormData
});
setRepresentationSubmit(
false
@@ -562,7 +620,7 @@ const RepCompleteSubmit = (props) => {
"appealType":
repFormData.appealType,
"repDetails":
repFormData,
repFormData
}),
setRepresentationSubmit(
false
@@ -609,6 +667,7 @@ const RepCompleteSubmit = (props) => {
"myrepresentations:rep-check-tandc-para-three"
)}
</p>
<p></p>
{checkRepDatePassed() ? (
<div className="govuk-warning-text noticebanner">
@@ -676,6 +735,35 @@ const RepCompleteSubmit = (props) => {
</>
) : (
<>
{repFormData.representationType ==
"Questionnaire" && (
<div className="govuk-checkboxes govuk-checkboxes--small">
<div className="govuk-checkboxes__item">
<input
name="pinswg_downloadQuestionnaire"
id="pinswg_downloadQuestionnaire"
type="checkbox"
className="govuk-checkboxes__input"
checked={
downloadQuestionnaire
}
onChange={(e) => {
setDownloadQuestionnaire(
e.target.checked
);
}}
/>
<label
className="govuk-label govuk-checkboxes__label"
htmlFor="pinswg_downloadQuestionnaire"
>
{t(
"myrepresentations:rep-check-tandc-para-five"
)}
</label>
</div>
</div>
)}
<div className="govuk-form-group">
<div className="govuk-checkboxes govuk-checkboxes--small">
<div className="govuk-checkboxes__item">
@@ -714,10 +802,11 @@ const RepCompleteSubmit = (props) => {
type="submit"
className="govuk-button"
data-module="govuk-button"
onClick={() => {
onClick={async () => {
// setRepresentationSubmitConfirmation(
// true
// );
await triggerQuestionnaireDownload();
setRepresentationSubmit(
true
);
@@ -754,7 +843,7 @@ const mapStateToProps = (state) => {
currentView: state.currentView,
myRepresentations: state.myRepresentations,
initialValues: state.currentView.caseReference.repDetails,
accountDetails: state.accountDetails,
accountDetails: state.accountDetails
//form: state.form,
};
};
@@ -763,7 +852,7 @@ const mapDispatchToProps = (dispatch) => {
return {
setCurrentReference: (currentReference) => {
dispatch(setCurrentReference(currentReference));
},
}
};
};
+1
View File
@@ -54,6 +54,7 @@
"rep-check-tandc-para-two": "Mae Penderfyniadau Cynllunio a'r Amgylchedd Cymru yn cymryd ei chyfrifoldebau diogelu data am y wybodaeth a roddwch i ni o ddifrif. I ddarganfod mwy am sut rydym yn defnyddio ac yn rheoli eich data personol, ewch i ein",
"rep-check-tandc-para-three": "Cadarnhaf fod pob adran or ffurflen hon wediu llenwin llawn a bod y manylion yn gywir hyd eithaf fy ngwybodaeth.",
"rep-check-tandc-para-four": "Cadarnhaf fy mod wedi darllen yr uchod.",
"rep-check-tandc-para-five": "Hoffwn lawrlwytho copi o'r holiadur hwn ar gyfer fy nghofnodion",
"rep-complete-heading": "Cyflwyno Sylwadau",
"rep-complete-para-one": "Diolch am gyflwyno eich sylw/sylwadau gan ddefnyddio'r Porth Gwaith Achos Apeliadau.",
"rep-complete-para-two": "Cofiwch fod angen i ni dderbyn unrhyw ddogfennau ategol o fewn y terfyn amser priodol ar gyfer yr achos. Rhaid i unrhyw beth y byddwch yn ei anfon atom gael ei farcio'n glir gyda chyfeirnod yr achos a chyfeiriad y safle (gan gynnwys y cod post, os yw'n hysbys).",
+1
View File
@@ -54,6 +54,7 @@
"rep-check-tandc-para-two": "Planning and Environment Decisions Wales takes its data protection responsibilities for the information you provide us with very seriously. To find out more about how we use and manage your personal data, please go to our",
"rep-check-tandc-para-three": "I confirm that all sections of this form have been fully completed and that the details are correct to the best of my knowledge.",
"rep-check-tandc-para-four": "I confirm I have read the above.",
"rep-check-tandc-para-five": "I wish to download a copy of this questionaire for my records",
"rep-complete-heading": "Submission of Representation",
"rep-complete-para-one": "Thank you for submitting your representation(s) using the Appeals Casework Portal.",
"rep-complete-para-two": "Please remember that any supporting documentation needs to be received by us within the appropriate deadline for the case. Anything you send to us must be clearly marked with the case reference number and the site address (including the postcode, where known).",
+36 -8
View File
@@ -107,27 +107,31 @@ import { consoleLogger } from "../../../actions";
// ApiProxy.post(async (req, res) => {
export default async function handler(req, res) {
const shouldDownload = req.query.download === "true";
var checkHash = req.query.hash;
let reqBodyobj = req.body; //JSON.parse(req.body);
let reqBodyobj =
typeof req.body === "string" ? JSON.parse(req.body) : req.body;
const cleanFilesList = (blobList) => {
let newblobList = blobList.filesList;
let newblobList = Array.isArray(blobList?.filesList)
? blobList.filesList
: [];
function removeNullObjects(arr) {
if (arr.length === 0) {
if (!Array.isArray(arr) || arr.length === 0) {
return arr;
}
return arr.filter((obj) => obj !== null);
}
function removeObjectsWithUndefinedKey(arr, key) {
if (arr.length === 0) {
if (!Array.isArray(arr) || arr.length === 0) {
return arr;
}
return arr.filter((obj) => obj[key] !== undefined);
}
function removeEmptyObjects(arr) {
if (arr.length === 0) {
if (!Array.isArray(arr) || arr.length === 0) {
return arr;
}
return arr.filter(
@@ -149,7 +153,7 @@ export default async function handler(req, res) {
return blobList;
};
reqBodyobj = reqBodyobj.hasOwnProperty("filesList")
reqBodyobj = reqBodyobj?.hasOwnProperty("filesList")
? cleanFilesList(reqBodyobj)
: reqBodyobj;
@@ -296,12 +300,26 @@ export default async function handler(req, res) {
// process.cwd() + `/tmp/${reqBodyobj.repfile_name}.pdf`
// );
const renderedPDF = await 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 renderedPDFStream = await ReactPDF.renderToStream(
<MyDocument docProps={reqBodyobj} />
);
const renderedPDFBuffer = await streamToBuffer(renderedPDFStream);
await createRepPDFBlob(
renderedPDF,
renderedPDFBuffer,
containerID,
caseRef,
reqBodyobj.repfile_name
@@ -312,6 +330,16 @@ export default async function handler(req, res) {
`${caseRef}/files/${reqBodyobj.repfile_name}.pdf`,
"\n/////////////////////////"
);
if (shouldDownload) {
const filename = `${reqBodyobj.repfile_name || "questionnaire"}.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({
status: "success",
data: data,