From 4d310fd6c12a2f5615d0bed43f6d720218d00567 Mon Sep 17 00:00:00 2001 From: rdbsolutions Date: Tue, 24 Jun 2025 10:52:06 +0100 Subject: [PATCH] link on portal for pdf download --- actions/index.js | 49 ++++++++ actions/selectQueryTypes.js | 4 +- components/myportal/topthree.js | 116 +++++++++-------- components/myportal/viewall.js | 61 ++++++++- components/pdftemplates/appealRow_pdf.js | 47 +++++-- .../pdftemplates/planningappeals78_pdf.js | 5 +- pages/api/email/getcaseref.js | 59 +++++++++ .../api/endpoint/getappealpdfdocuments_api.js | 109 ++++++++++++++++ pages/api/endpoint/getcasebyid_api.js | 59 +++++++++ pages/api/file/generateappealpdfcopy.js | 119 ++++++++++++++++++ 10 files changed, 564 insertions(+), 64 deletions(-) create mode 100644 pages/api/email/getcaseref.js create mode 100644 pages/api/endpoint/getappealpdfdocuments_api.js create mode 100644 pages/api/endpoint/getcasebyid_api.js create mode 100644 pages/api/file/generateappealpdfcopy.js diff --git a/actions/index.js b/actions/index.js index 3e43f62d..072e4fa0 100644 --- a/actions/index.js +++ b/actions/index.js @@ -572,6 +572,7 @@ export const getSearchDocumentDetails = (incidentID) => { .then((res) => res.data) .catch((error) => { consoleLogger(error); + throw error; // Important! }); }; @@ -586,6 +587,19 @@ export const getSearchDocumentTypes = (incidentID) => { }); }; +export const getAppealPDFDocs = (incidentID) => { + return axios + .get( + BASE_URL + + "/api/endpoint/getappealpdfdocuments_api?incidentid=" + + incidentID + ) + .then((res) => res.data) + .catch((error) => { + consoleLogger(error); + }); +}; + export const getSearchDocumentDetailsPaged = ( incidentID, pageNumber, @@ -1360,6 +1374,25 @@ export const getCase = (incidentID) => { }); }; +export const getCaseByID = (incidentID) => { + //console.log( + // "////////////// ------" + + // BASE_URL + + // "/api/endpoint/getcase_api?incidentID=" + + // incidentID + // ); + return axios + .get( + BASE_URL + "/api/endpoint/getcasebyid_api?incidentID=" + incidentID + ) + .then((res) => { + return res.data; + }) + .catch((error) => { + consoleLogger(error); + }); +}; + export const deleteMyRepresentations = (myRepresentationsID) => { var queryUrl = "/api/endpoint/deletemyrepresentations_api?myRepresentationsID=" + @@ -2160,3 +2193,19 @@ export const getPreferredLanguage = async (email) => { return error.response; }); }; + +export const getAppealPDFDocument = async (incidentid) => { + return axios + .get( + BASE_URL + + "/api/endpoint/getappealpdfdocuments_api?incidentid=" + + incidentid + ) + .then((res) => { + return res.data; + }) + .catch((error) => { + consoleLogger(error); + return error.response; + }); +}; diff --git a/actions/selectQueryTypes.js b/actions/selectQueryTypes.js index 0a0aa571..d72777a7 100644 --- a/actions/selectQueryTypes.js +++ b/actions/selectQueryTypes.js @@ -60,8 +60,8 @@ export const getSelectQuery = (appealTypeName) => { // process.env.RELAYPATH == "test-pedw-hc" // ); return process.env.RELAYPATH == "dev-pedw-hc" - ? "&$select=pinswg_eiarequired,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_localplanningauthority_value,pinswg_casedecisiondate,pinswg_dicision,pinswg_dateeventrequested,pinswg_finalcommentsduedate,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdate,pinswg_statementduedate,statuscode,pinswg_startdatetimeiftheevent,pinswg_representation_period_end_date,pinswg_extend_representation_date,pinswg_typeofevent" - : "&$select=pinswg_eiarequired,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_localplanningauthority_value,pinswg_casedecisiondate,pinswg_dicision,pinswg_dateeventrequested,pinswg_finalcommentsduedate,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdate,pinswg_statementduedate,statuscode,pinswg_startdatetimeiftheevent,pinswg_typeofevent"; + ? "&$select=pinswg_eiarequired,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,pinswg_siteaddresscounty,_pinswg_localplanningauthority_value,pinswg_casedecisiondate,pinswg_dicision,pinswg_dateeventrequested,pinswg_finalcommentsduedate,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdate,pinswg_statementduedate,statuscode,pinswg_startdatetimeiftheevent,pinswg_representation_period_end_date,pinswg_extend_representation_date,pinswg_typeofevent" + : "&$select=pinswg_eiarequired,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,pinswg_siteaddresscounty,_pinswg_localplanningauthority_value,pinswg_casedecisiondate,pinswg_dicision,pinswg_dateeventrequested,pinswg_finalcommentsduedate,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdate,pinswg_statementduedate,statuscode,pinswg_startdatetimeiftheevent,pinswg_typeofevent,pinswg_developmentdescription,pinswg_whyisinspectorneededonsite,pinswg_outlinehealthandsafetyissuesonsite,pinswg_additionalappealsmadedetails,pinswg_chosenprocedureexplanation,pinswg_dateofapplication,pinswg_lpaapplicationreference,pinswg_caseworkreason,pinswg_dateoflpadecision,pinswg_areaofsiteinhectaresdec,pinswg_floorspaceinsquaremeters,pinswg_ownershipcertificate,pinswg_siteviewablefromroad,pinswg_costappliedforindicator,pinswg_s106unilateralsubmitted"; break; case "pinswg_planningconditionss73s79s": return "&$select=pinswg_detailedeiascreeningrequiered,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdate,pinswg_statementduedate,statuscode,pinswg_startdateofevent,pinswg_typeofevent"; diff --git a/components/myportal/topthree.js b/components/myportal/topthree.js index 16f75fe6..5da0793e 100644 --- a/components/myportal/topthree.js +++ b/components/myportal/topthree.js @@ -28,6 +28,11 @@ import { setWatchedCasesDetails, } from "../../store/watchedCases/action"; +import { planningappeals78_pdf } from "../../components/pdftemplates/planningappeals78_pdf"; +import { other_pdf } from "../../components/pdftemplates/other_pdf"; + +import { PDFDownloadLink, pdf } from "@react-pdf/renderer"; + const TopThree = (props) => { let { t } = useTranslation(); const { @@ -188,6 +193,29 @@ const TopThree = (props) => { } }; + const downloadPDF = async (docProps, ticketnumber) => { + try { + const res = await fetch("/api/file/generateappealpdfcopy", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ docProps }), + }); + + if (!res.ok) throw new Error("PDF generation failed"); + + const blob = await res.blob(); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `Appeal_${ticketnumber || "summary"}.pdf`; + a.click(); + URL.revokeObjectURL(url); + } catch (err) { + console.error("Download error:", err); + alert("Could not generate PDF."); + } + }; + let topThreeOnlyArr = showTopThreeArr.slice(0, 3); Object.keys(topThreeOnlyArr).map((key, index) => { topthreeRow.push( @@ -197,10 +225,44 @@ const TopThree = (props) => { {t("myportal:case-reference")}: {topThreeType == "myCases" && showTopThreeArr[key].pinswg_publishtoweb === false ? ( - - {showTopThreeArr[key].ticketnumber}{" "} - - {t("myportal:appeal-pending-label")} - + <> + + {showTopThreeArr[key].ticketnumber}{" "} + + {" "} + - {t("myportal:appeal-pending-label")} + + + { + confirm( + "Do you want to download a pdf copy of your appeal for " + + showTopThreeArr[key] + .ticketnumber + ) && + downloadPDF( + showTopThreeArr[key].incidentid, + showTopThreeArr[key] + .ticketnumber + ); + }} + > + {showTopThreeArr[key].ticketnumber} + + ) : ( { )} - {/* {topThreeType == "watchedCases" && !props.myReps && ( -
- -
- )} - {topThreeType == "awaitingSubmission" && ( -
- -
- )} */} ); }); diff --git a/components/myportal/viewall.js b/components/myportal/viewall.js index 9fd59505..a625b871 100644 --- a/components/myportal/viewall.js +++ b/components/myportal/viewall.js @@ -116,6 +116,30 @@ const ViewAllResults = (props) => { : props[viewAllKey][viewAllKey + "Details"] || []; let resultsRowArr = []; + + const downloadPDF = async (docProps, ticketnumber) => { + try { + const res = await fetch("/api/file/generateappealpdfcopy", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ docProps }), + }); + + if (!res.ok) throw new Error("PDF generation failed"); + + const blob = await res.blob(); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `Appeal_${ticketnumber || "summary"}.pdf`; + a.click(); + URL.revokeObjectURL(url); + } catch (err) { + console.error("Download error:", err); + alert("Could not generate PDF."); + } + }; + const buildResultsRowArr = () => { resultsArr.map((item, key) => { let detailsObj = jsonpath({ @@ -149,10 +173,39 @@ const ViewAllResults = (props) => {
{viewAllKey == "myCases" && resultsArr[key].pinswg_publishtoweb === false ? ( -
- {resultsArr[key].ticketnumber}{" "} - {t("myportal:appeal-pending-label")} -
+ <> +
+ {resultsArr[key].ticketnumber}{" "} + {t("myportal:appeal-pending-label")} +
+ { + confirm( + "Do you want to download a pdf copy of your appeal for " + + resultsArr[key].ticketnumber + ) && + downloadPDF( + resultsArr[key].incidentid, + resultsArr[key].ticketnumber + ); + }} + > + {resultsArr[key].ticketnumber} + + ) : ( <> { doc ); + // console.log( + // fieldtype[0].value, + // " - ", + // datafieldname[0].value, + // " - ", + // props.props[datafieldname[0].value], + // " - ", + // rows[0].value + // ); return ( <> {(() => { @@ -226,6 +235,10 @@ export const AppealRow_pdf = (props) => { ); break; case "{E0DECE4B-6FC8-4a8f-A065-082708572369}": + const fieldName = datafieldname[0].value; + // console.log( + // JSON.stringify(props.props, null, 2) + // ); return ( { .value ] } + {props.props[fieldName] ?? + props.props.caseObj?.[ + fieldName + ]} @@ -401,7 +418,13 @@ export const AppealRow_pdf = (props) => { // props.filesList // ); - const blobList = props.filesList; + //const blobList = props.filesList || []; + + const blobList = Array.isArray( + props.filesList?.value + ) + ? props.filesList.value + : []; return ( { styles.documentList } > - {blob.name} -{" "} - {bytesToSize( - blob.size - )}{" "} + {blob.name} + {blob?.size && + " - " + + bytesToSize( + blob?.size + )}{" "} {"\n"} )} @@ -456,6 +481,10 @@ export const AppealRow_pdf = (props) => { ); break; case "{0273EDBD-AC1D-40d3-9FB2-095C621B552D}": + // console.log( + // datafieldname[0].value, + // props.props[datafieldname[0].value] + // ); return ( { marginBottom: 10, }} > - {props.props.hasOwnProperty( + {(props.props.hasOwnProperty( datafieldname[0].value ) && + props.props[ + datafieldname[0] + .value + ] == null) || props.props[ datafieldname[0].value - ] == null ? ( + ] == undefined ? ( N/A diff --git a/components/pdftemplates/planningappeals78_pdf.js b/components/pdftemplates/planningappeals78_pdf.js index f98ef065..8ee1d5da 100644 --- a/components/pdftemplates/planningappeals78_pdf.js +++ b/components/pdftemplates/planningappeals78_pdf.js @@ -519,7 +519,10 @@ export const planningappeals78_pdf = ({ docProps, pickListData }) => { LPA - {values.caseObj.pinswg_lpaname} + {values.caseObj.pinswg_lpaname || + values.caseObj[ + "_pinswg_associatedlpa_value@OData.Community.Display.V1.FormattedValue" + ]} {/* { + var hashlink = CryptoJS.HmacSHA256( + queryPath, + CryptoJS.enc.Hex.parse(WORDKEY) + ); + hashlink = hashlink.toString(CryptoJS.enc.Hex); + + //return "&hash=" + hashlink; + + return (queryPath.indexOf("?") > -1 ? "&hash=" : "?hash=") + hashlink; +}; + +function flattenWatchlistEntry(entry) { + const flattened = { ...entry }; + + if (entry.pinswg_Contact) { + flattened.contactid = entry.pinswg_Contact.contactid; + flattened.contact_email = entry.pinswg_Contact.emailaddress1; + delete flattened.pinswg_Contact; // Optional: remove nested object + } + + return flattened; +} + +export default async function ApiProxy(req, res) { + const incidentID = req.query.incidentid; + var token = await getToken(); + + var queryUrl = + "pinswg_watchlists?$count=true&$filter=pinswg_emailnotifications ne null&$expand=pinswg_Contact($select=contactid,emailaddress1)&$select=pinswg_emailnotifications,_pinswg_watchedcase_value"; + + return axios + .get( + WEBAPI_URL + queryUrl + hashAPIPath(queryUrl), + azureHeaders(token.access_token) + ) + .then(({ data }) => { + var dataStr; + + const flattenedResults = data.value.map(flattenWatchlistEntry); + + res.status(200).json(flattenedResults); + }) + .catch((error) => { + consoleLogger(error); + res.status(400).json(error); + }); +} diff --git a/pages/api/endpoint/getappealpdfdocuments_api.js b/pages/api/endpoint/getappealpdfdocuments_api.js new file mode 100644 index 00000000..7f74b9bb --- /dev/null +++ b/pages/api/endpoint/getappealpdfdocuments_api.js @@ -0,0 +1,109 @@ +/** + * @swagger + * /api/endpoint/getsearchdocumentTypes_api: + * get: + * tags: [Search,] + * description: Returns types of documents for a case + * parameters: + * - name: incidentid + * in: query + * required: true + * example: 50b533aa-43f1-ec11-aade-00224800be9c + * schema: + * type: string + * responses: + * 200: + * description: Success + */ + +import axios from "axios"; +import CryptoJS from "crypto-js"; +import { azureHeaders, consoleLogger, getToken } from "../../../actions"; + +const WORDKEY = process.env.HASHKEY; + +const WEBAPI_URL = + process.env.RELAY_ROOT || + "https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/"; + +const hashAPIPath = (queryPath) => { + var hashlink = CryptoJS.HmacSHA256( + queryPath, + CryptoJS.enc.Hex.parse(WORDKEY) + ); + hashlink = hashlink.toString(CryptoJS.enc.Hex); + + //return "&hash=" + hashlink; + + return (queryPath.indexOf("?") > -1 ? "&hash=" : "?hash=") + hashlink; +}; + +const groupArray = (arr) => { + const map = new Map(); + + // Count occurrences of each object + arr.forEach((obj) => { + const key = JSON.stringify(obj); + map.set(key, (map.get(key) || 0) + 1); + }); + + // Create a new array to store unique objects with count + const result = []; + map.forEach((count, key) => { + const obj = JSON.parse(key); + + const transformedObj = { + pinswg_isharedocumentlocations: obj.pinswg_isharedocumentlocations, // Change 'id' to 'userId' + pinswg_isharedocumentlocationsLabel: + obj[ + "pinswg_isharedocumentlocations@OData.Community.Display.V1.FormattedValue" + ], // Change 'name' to 'userName' + count: count, // Add 'count' as 'occurrences' + }; + + result.push(transformedObj); + }); + + return result; +}; + +export default async function ApiProxy(req, res) { + var incidentID = req.query.incidentid; + var token = await getToken(); + + var queryUrl = + "pinswg_documents?$count=true&$filter=_pinswg_documentids_value eq " + + incidentID + + " and not(contains(pinswg_name,'_Appeal_Form.pdf'))&$select=pinswg_name,pinswg_isharedocumentlocations"; + + console.log("docu: ", WEBAPI_URL + queryUrl + hashAPIPath(queryUrl)); + + return axios + .get( + WEBAPI_URL + queryUrl + hashAPIPath(queryUrl), + azureHeaders(token.access_token) + ) + .then(({ data }) => { + // //delete data["pinswg_documentid"]; + // data.value.forEach(function (element) { + // delete element["pinswg_documentid"]; + // }); + + // var dataArr = groupArray(data.value); + + // // var dataStr; + // // _.has(data, "@odata.nextLink") == true && + // // ((dataStr = JSON.stringify(data["@odata.nextLink"])), + // // (data["@odata.nextLink"] = dataStr.split("/v8.2/")[1])); + + data.value.forEach((item) => { + item.name = item.pinswg_name; + }); + + res.status(200).json(data); + }) + .catch((error) => { + consoleLogger(error); + res.status(400).json(error); + }); +} diff --git a/pages/api/endpoint/getcasebyid_api.js b/pages/api/endpoint/getcasebyid_api.js new file mode 100644 index 00000000..59537847 --- /dev/null +++ b/pages/api/endpoint/getcasebyid_api.js @@ -0,0 +1,59 @@ +/** + * @swagger + * /api/endpoint/getcase_api: + * get: + * tags: [Case] + * summary: Not used + * description: Get case + * responses: + * 200: + * description: Success + */ + +import axios from "axios"; +import CryptoJS from "crypto-js"; +import _ from "lodash"; +import { getToken, azureHeaders, consoleLogger } from "../../../actions"; + +const WORDKEY = process.env.HASHKEY; + +const WEBAPI_URL = + process.env.RELAY_ROOT || + "https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/"; + +const hashAPIPath = (queryPath) => { + var hashlink = CryptoJS.HmacSHA256( + queryPath, + CryptoJS.enc.Hex.parse(WORDKEY) + ); + hashlink = hashlink.toString(CryptoJS.enc.Hex); + + //return "&hash=" + hashlink; + + return (queryPath.indexOf("?") > -1 ? "&hash=" : "?hash=") + hashlink; +}; + +export default async function ApiProxy(req, res) { + var incidentID = req.query.incidentID; + var token = await getToken(); + var queryUrl = "incidents(" + incidentID + ")"; + + //console.log(WEBAPI_URL + queryUrl + hashAPIPath(queryUrl)); + + return axios + .get( + WEBAPI_URL + queryUrl + hashAPIPath(queryUrl), + azureHeaders(token.access_token) + ) + .then(({ data }) => { + var dataStr; + _.has(data, "@odata.nextLink") == true && + ((dataStr = JSON.stringify(data["@odata.nextLink"])), + (data["@odata.nextLink"] = dataStr.split("/v8.2/")[1])); + res.status(200).json([data]); + }) + .catch((error) => { + consoleLogger(error); + res.status(400).json(error); + }); +} diff --git a/pages/api/file/generateappealpdfcopy.js b/pages/api/file/generateappealpdfcopy.js new file mode 100644 index 00000000..9f23bc92 --- /dev/null +++ b/pages/api/file/generateappealpdfcopy.js @@ -0,0 +1,119 @@ +import { + createContainer, + getContainers, + getBlobs, + createRepBlob, + uploadFile, + uploadPDFAppealFiles, + downloadProgressFile, + getProgressBlobs, + getTempCaseBlob, + createAppealPDFBlob, +} from "../../../actions/azurestorage"; +import { + hashAPIPath, + getSearchDocumentDetails, + getCaseByID, + getPortalModuleDetails, + getAppealPDFDocs, +} from "../../../actions"; +import { getFormCollectionByID } from "../../../components/utils"; +import ReactPDF, { + Document, + Page, + Text, + View, + StyleSheet, + PDFViewer, + pdf, +} from "@react-pdf/renderer"; + +import middleware from "../middleware/middleware"; +import nextConnect from "next-connect"; +import fs from "fs"; +import path from "path"; +import { getPickLists } from "../../../actions"; +import { planningappeals78_pdf } from "../../../components/pdftemplates/planningappeals78_pdf"; +import { finalComments_pdf } from "../../../components/pdftemplates/finalComments_pdf"; +import { statement_pdf } from "../../../components/pdftemplates/statement_pdf"; +import { writtenStatement_pdf } from "../../../components/pdftemplates/writtenStatement_pdf"; +import { other_pdf } from "../../../components/pdftemplates/other_pdf"; + +const getDetails = (resultsObj, detailsType) => { + if (!Array.isArray(resultsObj)) { + console.warn("getDetails: resultsObj is not an array"); + return Promise.resolve([]); + } + + const detailsPromises = resultsObj + .filter((searchDetail) => searchDetail.pinswg_appealcasetype != null) + .map((searchDetail) => { + const caseID = searchDetail.ticketnumber; + const appealType = searchDetail.pinswg_appealcasetype; + + const form = getFormCollectionByID(appealType); + if (!form) { + console.warn("No form found for appeal type:", appealType); + return null; + } + + return getPortalModuleDetails(form.LogicalCollectionName, caseID); + }) + .filter(Boolean); // Remove any `null` if form not found + + return Promise.all(detailsPromises); +}; + +export default async function handler(req, res) { + const incidentid = req.body?.docProps; + if (!incidentid) return res.status(400).send("Missing incident ID"); + + const caseObj = await getCaseByID(incidentid); + if (!Array.isArray(caseObj) || caseObj.length === 0) + return res.status(404).send("Case not found"); + + const caseDetailsObj = await getDetails(caseObj, "myCases"); + const fileList = (await getAppealPDFDocs(incidentid)) || []; + + // Inject into the first item in `value` array + + if (caseDetailsObj[0]?.value?.[0]) { + caseDetailsObj[0].value[0].pinswg_developmentdescription = + caseObj[0].description; + caseDetailsObj[0].value[0].caseObj = caseObj[0]; + caseDetailsObj[0].value[0].filesList = fileList; + } + + const whichForm = getFormCollectionByID(caseObj[0].pinswg_appealcasetype); + const pickListData = await getPickLists(whichForm.UrlName); + + const appealTypeMap = { + 846040000: planningappeals78_pdf, + 846040004: planningappeals78_pdf, + }; + + //console.log(caseDetailsObj[0].value[0]); + + const MyDocument = (values) => { + const type = caseObj[0].pinswg_appealcasetype; + const renderDocument = appealTypeMap[type] || other_pdf; + return renderDocument(values); + }; + + const pdfComponent = MyDocument({ + docProps: caseDetailsObj[0].value[0], + pickListData, + }); + + const pdfBuffer = await pdf(pdfComponent).toBuffer(); + + const repDate = new Date(); + const formattedDate = repDate.toISOString().split("T")[0]; + + res.setHeader("Content-Type", "application/pdf"); + res.setHeader( + "Content-Disposition", + `attachment; filename=${formattedDate}_-_Appeal_Form.pdf` + ); + res.send(pdfBuffer); +}