From c0dbaa2c3f56fcce51165b210d804808cdb44f3b Mon Sep 17 00:00:00 2001 From: rdbsolutions Date: Thu, 11 Sep 2025 10:49:47 +0100 Subject: [PATCH 01/13] reps files to use ticket number not title for case ref --- pages/myportal/representation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/myportal/representation.js b/pages/myportal/representation.js index 8878254d..15fbe897 100644 --- a/pages/myportal/representation.js +++ b/pages/myportal/representation.js @@ -317,7 +317,7 @@ export const getServerSideProps = wrapper.getServerSideProps( const repsFileListObj = await getRepsFilesBlobs( result.containerID, - result.caseRef, + result.ticketnumber, result.repfile_name ); From f8769d0bf15f5d0ae7cc76bfa2b899eef496d9c1 Mon Sep 17 00:00:00 2001 From: rdbsolutions Date: Thu, 11 Sep 2025 10:50:45 +0100 Subject: [PATCH 02/13] url encode/decode correctly paths for hashed values --- actions/azurestorage.js | 50 +++++++++++++++++----------------- actions/index.js | 18 ++++++------ pages/api/file/deleteblob.js | 7 ++--- pages/api/file/downloadblob.js | 10 ++----- 4 files changed, 37 insertions(+), 48 deletions(-) diff --git a/actions/azurestorage.js b/actions/azurestorage.js index 68206580..75821451 100644 --- a/actions/azurestorage.js +++ b/actions/azurestorage.js @@ -210,31 +210,31 @@ export const getBlobs = async (containerName, casefolderID) => { "/api/file/downloadblob?container=" + containerName + "&casefolderID=" + - casefolderID + + encodeURIComponent(casefolderID) + "&blobname=" + - blob.name, + encodeURIComponent(blob.name), "hashedfilepath": hashAPIPath( "/api/file/downloadblob?container=" + containerName + "&casefolderID=" + - casefolderID + + encodeURIComponent(casefolderID) + "&blobname=" + - blob.name.split("/")[2] + encodeURIComponent(blob.name.split("/")[2]) ), "deletepath": "/api/file/deleteblob?container=" + containerName + "&casefolderID=" + - casefolderID + + encodeURIComponent(casefolderID) + "&blobname=" + - blob.name.split("/")[2], + encodeURIComponent(blob.name.split("/")[2]), "hasheddeletepath": hashAPIPath( "/api/file/deleteblob?container=" + containerName + "&casefolderID=" + - casefolderID + + encodeURIComponent(casefolderID) + "&blobname=" + - blob.name.split("/")[2] + encodeURIComponent(blob.name.split("/")[2]) ), "hashgetblobs": hashAPIPath( "/api/file/getbloblist?container=" + @@ -1235,17 +1235,17 @@ export const getProgressBlobs = async (containerName, caseReference) => { "/api/file/downloadblob?container=" + containerName + "&casefolderID=" + - caseReference + + encodeURIComponent(caseReference) + "&blobname=" + - blob.name.split("/")[1] + encodeURIComponent(blob.name.split("/")[1]) ), "hasheddeletepath": hashAPIPath( "/api/file/deleteblob?container=" + containerName + "&casefolderID=" + - caseReference + + encodeURIComponent(caseReference) + "&blobname=" + - blob.name.split("/")[1] + encodeURIComponent(blob.name.split("/")[1]) ), "hashgetblobs": hashAPIPath( "/api/file/getbloblist?container=" + @@ -1406,20 +1406,20 @@ export const getRepsFilesBlobs = async ( "/api/file/downloadblob?container=" + containerName + "&casefolderID=" + - blob.name.split("/")[0] + - "/" + - blob.name.split("/")[1] + + encodeURIComponent( + blob.name.split("/")[0] + "/" + blob.name.split("/")[1] + ) + "&blobname=" + - blob.name.split("/")[3], + encodeURIComponent(blob.name.split("/")[3]), "hashedfilepath": hashAPIPath( "/api/file/downloadblob?container=" + containerName + "&casefolderID=" + - blob.name.split("/")[0] + - "/" + - blob.name.split("/")[1] + + encodeURIComponent( + blob.name.split("/")[0] + "/" + blob.name.split("/")[1] + ) + "&blobname=" + - blob.name + encodeURIComponent(blob.name.split("/")[3]) ), "deletepath": "/api/file/deleteblob?container=" + @@ -1434,11 +1434,11 @@ export const getRepsFilesBlobs = async ( "/api/file/deleteblob?container=" + containerName + "&casefolderID=" + - blob.name.split("/")[0] + - "/" + - blob.name.split("/")[1] + + encodeURIComponent( + blob.name.split("/")[0] + "/" + blob.name.split("/")[1] + ) + "&blobname=" + - blob.name.split("/")[3] + encodeURIComponent(blob.name.split("/")[3]) ), "hashgetblobs": hashAPIPath( "/api/file/getbloblist?container=" + @@ -1450,7 +1450,7 @@ export const getRepsFilesBlobs = async ( ), }); } - //console.log("blobObj:", blobObj); + console.log("blobObj:", blobObj); return blobObj; }; diff --git a/actions/index.js b/actions/index.js index 8f32f50f..e995a893 100644 --- a/actions/index.js +++ b/actions/index.js @@ -1762,9 +1762,9 @@ export const deleteBlob = async ( "/api/file/deleteblob?container=" + containerName + "&casefolderID=" + - casefolderID + + encodeURIComponent(casefolderID) + "&blobname=" + - blobName + + encodeURIComponent(blobName) + deleteblobhash ); return res.data; @@ -1785,11 +1785,9 @@ export const deleteRepBlob = async ( "/api/file/deleteblob?container=" + containerName + "&casefolderID=" + - casefolderID + - "/" + - filenamePrefix + + encodeURIComponent(casefolderID + "/" + filenamePrefix) + "&blobname=" + - blobName + + encodeURIComponent(blobName) + deleteblobhash ); return res.data; @@ -1843,14 +1841,14 @@ export const getProgressFromBlob = async (containerName, casereference) => { const res = await axios.get( BASE_URL + "/api/file/getprogressobjblob?container=" + - containerName + + encodeURIComponent(containerName) + "&casefolderID=" + - casereference + + encodeURIComponent(casereference) + hashAPIPath( "/api/file/getprogressobjblob?container=" + - containerName + + encodeURIComponent(containerName) + "&casefolderID=" + - casereference + encodeURIComponent(casereference) ) ); return res.data; diff --git a/pages/api/file/deleteblob.js b/pages/api/file/deleteblob.js index 64ca4d30..465dc167 100644 --- a/pages/api/file/deleteblob.js +++ b/pages/api/file/deleteblob.js @@ -17,12 +17,9 @@ ApiProxy.get(async (req, res) => { "/api/file/deleteblob?container=" + containerName + "&casefolderID=" + - casefolderID + + encodeURIComponent(casefolderID) + "&blobname=" + - blobName; - - //console.log(hashAPIPath(checkquerypath), checkHash); - //console.log(hashAPIPath(checkquerypath) == "&hash=" + checkHash); + encodeURIComponent(blobName); if (hashAPIPath(checkquerypath) == "&hash=" + checkHash) { await deleteBlob( diff --git a/pages/api/file/downloadblob.js b/pages/api/file/downloadblob.js index 5ad78d18..9e43fdb7 100644 --- a/pages/api/file/downloadblob.js +++ b/pages/api/file/downloadblob.js @@ -20,15 +20,9 @@ ApiProxy.get(async (req, res) => { "/api/file/downloadblob?container=" + containerName + "&casefolderID=" + - casefolderID + + encodeURIComponent(casefolderID) + "&blobname=" + - blobName.trim(); - - // console.log(checkquerypath); - - // console.log(hashAPIPath(checkquerypath)); - - // console.log(req.query); + encodeURIComponent(blobName.trim()); if (hashAPIPath(checkquerypath) == "&hash=" + checkHash) { const bloblocation = From e9fc8816c1c7ac865a788a5586941b444b398854 Mon Sep 17 00:00:00 2001 From: rdbsolutions Date: Thu, 11 Sep 2025 10:51:14 +0100 Subject: [PATCH 03/13] url encode values corect for hash and remove .zip files allowed --- components/case/representation/representationElements.js | 7 +------ components/elements/index.js | 9 ++------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/components/case/representation/representationElements.js b/components/case/representation/representationElements.js index 9fade1e2..5108923d 100644 --- a/components/case/representation/representationElements.js +++ b/components/case/representation/representationElements.js @@ -939,10 +939,6 @@ export const RenderFileUpload = (field) => { case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": return "/assets/images/documenttypes/xlsx.png"; break; - - case "application/zip": - return "/assets/images/documenttypes/zip.png"; - break; case "image/jpeg": return "/assets/images/documenttypes/jpg.png"; case "image/png": @@ -1091,7 +1087,6 @@ export const RenderFileUpload = (field) => { [".docx"], "image/tiff": [".tif", ".tiff"], "image/jpeg": [".jpg", ".jpeg"], - "application/zip": [".zip"], "image/png": [".png"], "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [".xlsx"], @@ -1338,7 +1333,7 @@ export const RenderFileUpload = (field) => { ?.values?.repfile_name ) + "&blobname=" + - blob.name + + encodeURIComponent(blob.name) + blob.hashedfilepath } className="govuk-body govuk-!-font-size-14 govuk-!-padding-left-5 govuk-link" diff --git a/components/elements/index.js b/components/elements/index.js index 931edf43..bf38eb10 100644 --- a/components/elements/index.js +++ b/components/elements/index.js @@ -1973,10 +1973,6 @@ const RenderFileUpload = (field) => { case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": return "/assets/images/documenttypes/xlsx.png"; break; - - case "application/zip": - return "/assets/images/documenttypes/zip.png"; - break; case "image/jpeg": return "/assets/images/documenttypes/jpg.png"; case "image/png": @@ -2145,7 +2141,6 @@ const RenderFileUpload = (field) => { [".docx"], "image/tiff": [".tif", ".tiff"], "image/jpeg": [".jpg", ".jpeg"], - "application/zip": [".zip"], "image/png": [".png"], "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [".xlsx"], @@ -2383,9 +2378,9 @@ const RenderFileUpload = (field) => { "/api/file/downloadblob?container=" + field.containerID + "&casefolderID=" + - field.ticketnumber + + encodeURIComponent(field.ticketnumber) + "&blobname=" + - blob.name + + encodeURIComponent(blob.name) + blob.hashedfilepath } className="govuk-body govuk-!-font-size-14 govuk-!-padding-left-5 govuk-link" From 17042d98366781cf8537a893159cb0cca40ffb20 Mon Sep 17 00:00:00 2001 From: rdbsolutions Date: Thu, 11 Sep 2025 10:51:47 +0100 Subject: [PATCH 04/13] update copy to remove allowed zip files --- locales/cy/myrepresentations.json | 2 +- locales/cy/newappeal.json | 2 +- locales/en/myrepresentations.json | 2 +- locales/en/newappeal.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/cy/myrepresentations.json b/locales/cy/myrepresentations.json index 63e755cc..19176379 100644 --- a/locales/cy/myrepresentations.json +++ b/locales/cy/myrepresentations.json @@ -23,7 +23,7 @@ "questionnaire-publish-policy-label": "Sylwch y gallai'r holl ddogfennau holiadur gael eu cyhoeddi yn unol â'n Polisi Cyhoeddi. Os bydd eich holiadur yn cynnwys unrhyw wybodaeth sensitif neu wybodaeth a allai fod yn ddifenwol, efallai y caiff ei olygu cyn ei gyhoeddi", "add-files-label": "Ychwanegwch eich ffeiliau", "fileupload-drop-label": "Llusgwch a gollyngwch eich ffeiliau yma.", - "fileupload-file-list-label": "Dim ond ffeiliau .pdf, .doc, .docx, .xlsx, .tif, .tiff, .jpeg, .jpg neu .zip a dderbynnir", + "fileupload-file-list-label": "Dim ond ffeiliau .pdf, .doc, .docx, .xlsx, .tif, .tiff, .jpeg neu .jpg a dderbynnir", "fileupload-file-error-filesize-label": "yn rhy fawr. Llwythwch ffeil lai i fyny.", "fileupload-file-error-invalid-type-label": "mae ganddo fath annilys. Uwchlwythwch fath a ganiateir.", "fileupload-file-error-invalid-label": "yn annilys", diff --git a/locales/cy/newappeal.json b/locales/cy/newappeal.json index 3fc577ac..5d9eded1 100644 --- a/locales/cy/newappeal.json +++ b/locales/cy/newappeal.json @@ -82,7 +82,7 @@ "new-appeal-procedure-option-inperson": "Wyneb yn wyneb", "new-appeal-procedure-option-hybrid": "Hybrid", "new-appeal-fileupload-drop-label": "Llusgwch a gollyngwch eich ffeiliau yma.", - "new-appeal-fileupload-file-list-label": "Dim ond ffeiliau .pdf, .doc, .docx, .xlsx .tif, .tiff, .jpeg, .jpg neu .zip a dderbynnir", + "new-appeal-fileupload-file-list-label": "Dim ond ffeiliau .pdf, .doc, .docx, .xlsx, .tif, .tiff, .jpeg neu .jpg a dderbynnir", "new-appeal-fileupload-file-error-filesize-label": "yn rhy fawr. Llwythwch ffeil lai i fyny.", "new-appeal-fileupload-file-error-invalid-type-label": "mae ganddo fath annilys. Uwchlwythwch fath a ganiateir.", "new-appeal-fileupload-file-error-invalid-label": "yn annilys", diff --git a/locales/en/myrepresentations.json b/locales/en/myrepresentations.json index e0723fe7..8c480993 100644 --- a/locales/en/myrepresentations.json +++ b/locales/en/myrepresentations.json @@ -23,7 +23,7 @@ "questionnaire-publish-policy-label": "Please note that all questionnaire documents may be published in line with our Publishing Policy. Should your questionnaire include any sensitive information or potentially defamatory information, it may be redacted before publishing", "add-files-label": "Add your files", "fileupload-drop-label": "Drag and drop your files here.", - "fileupload-file-list-label": "Only .pdf, .doc, .docx, .xlsx, .tif, .tiff, .jpeg, .jpg or .zip files will be accepted", + "fileupload-file-list-label": "Only .pdf, .doc, .docx, .xlsx, .tif, .tiff, .jpeg or .jpg files will be accepted", "fileupload-file-error-filesize-label": "is too large. Please upload a smaller file.", "fileupload-file-error-invalid-type-label": "has an invalid type. Please upload an allowed type.", "fileupload-file-error-invalid-label": "is invalid", diff --git a/locales/en/newappeal.json b/locales/en/newappeal.json index 0dbdb358..3cef9e71 100644 --- a/locales/en/newappeal.json +++ b/locales/en/newappeal.json @@ -82,7 +82,7 @@ "new-appeal-procedure-option-inperson": "In person", "new-appeal-procedure-option-hybrid": "Hybrid", "new-appeal-fileupload-drop-label": "Drag and drop your files here.", - "new-appeal-fileupload-file-list-label": "Only .pdf, .doc, .docx, .xlsx, .tif, .tiff, .jpeg, .jpg or .zip files will be accepted", + "new-appeal-fileupload-file-list-label": "Only .pdf, .doc, .docx, .xlsx, .tif, .tiff, .jpeg or .jpg files will be accepted", "new-appeal-fileupload-file-error-filesize-label": "is too large. Please upload a smaller file.", "new-appeal-fileupload-file-error-invalid-type-label": "has an invalid type. Please upload an allowed type.", "new-appeal-fileupload-file-error-invalid-label": "is invalid", From cb948a160bb03baa45586009d2bca83cc442ba18 Mon Sep 17 00:00:00 2001 From: rdbsolutions Date: Tue, 23 Sep 2025 12:44:21 +0100 Subject: [PATCH 05/13] updated admin tabs and filtering --- actions/index.js | 21 +- components/admin/tabs/accounts.js | 2 + components/admin/tabs/documents.js | 265 ++++++++++++++++++---- pages/admin/index.js | 71 +++--- pages/api/admin/getlatestdocuments_api.js | 26 ++- 5 files changed, 299 insertions(+), 86 deletions(-) diff --git a/actions/index.js b/actions/index.js index e995a893..fd04e4a8 100644 --- a/actions/index.js +++ b/actions/index.js @@ -2144,8 +2144,25 @@ export const getNewDocumentsPaged = async ( fieldSort, showNumberOfRecords, documentType, + documentOrigin, selectedWeeks ) => { + console.log( + "/api/admin/getlatestdocuments_api?pageNumber=" + + pageNumber + + "&orderby=" + + orderBy + + "&fieldSort=" + + fieldSort + + "&showNumberOfRecords=" + + showNumberOfRecords + + "&documentType=" + + documentType + + "&numberWeeks=" + + selectedWeeks + + "&documentOrigin=" + + documentOrigin + ); return axios .get( "/api/admin/getlatestdocuments_api?pageNumber=" + @@ -2159,7 +2176,9 @@ export const getNewDocumentsPaged = async ( "&documentType=" + documentType + "&numberWeeks=" + - selectedWeeks + selectedWeeks + + "&documentOrigin=" + + documentOrigin ) .then((res) => { return res.data; diff --git a/components/admin/tabs/accounts.js b/components/admin/tabs/accounts.js index 97498248..8d802860 100644 --- a/components/admin/tabs/accounts.js +++ b/components/admin/tabs/accounts.js @@ -26,6 +26,8 @@ export default function AccountsTab({ userData }) { > {user.email} +
+ {user.id}
Last accessed: {user.created} diff --git a/components/admin/tabs/documents.js b/components/admin/tabs/documents.js index 23818da2..94bc359a 100644 --- a/components/admin/tabs/documents.js +++ b/components/admin/tabs/documents.js @@ -7,6 +7,9 @@ import { connect } from "react-redux"; import transLookup from "../../../data/lookuptranslations.json"; import { formatDates } from "../../utils"; import PaginationControl from "../../../components/case/pagination"; +import DocumentLink from "../../utils/downloads"; +import { useDownloadQueue } from "../../utils/downloadmanager"; + import { sendGAEvent } from "@next/third-parties/google"; import { @@ -170,6 +173,21 @@ const getDocumentTypes = [ }, ]; +const getOrigin = [ + { + "Value": 846040000, + "Label": "MDU", + }, + { + "Value": 846040001, + "Label": "Portal", + }, + { + "Value": 846040002, + "Label": "Manual", + }, +]; + const DocumentsTab = (props) => { let { t, lang } = useTranslation(); const firstRender = useRef(false); @@ -189,12 +207,15 @@ const DocumentsTab = (props) => { const { locale } = router; var documentDetailsArr = documentDetailsObj || []; - + const { startDownload, getStatus } = useDownloadQueue(3); const [selectedOption, setSelectedOption] = useState(10); const [selectedWeeks, setSelectedWeeks] = useState(1); const [documentTypes, setDocumentTypes] = useState(getDocumentTypes); + const [documentOrigin, setDocumentOrigin] = useState(getOrigin); const [selectedDocumentType, setSelectedDocumentType] = useState("all"); + const [selectedDocumentOrigin, setSelectedDocumentOrigin] = useState("all"); const [selectAll, setSelectAll] = useState(false); + const [selectOriginAll, setSelectOriginAll] = useState(false); const [checkedItems, setCheckedItems] = useState( documentTypes.reduce((acc, item) => { @@ -203,6 +224,13 @@ const DocumentsTab = (props) => { }, {}) ); + const [checkedOriginItems, setCheckedOriginItems] = useState( + documentOrigin.reduce((acc, item) => { + acc[item.pinswg_origin] = false; + return acc; + }, {}) + ); + const handleCheckboxChange = (e) => { const { name, checked } = e.target; @@ -213,6 +241,16 @@ const DocumentsTab = (props) => { setSelectAll(false); }; + const handleCheckboxOriginChange = (e) => { + const { name, checked } = e.target; + + setCheckedOriginItems((prev) => ({ + ...prev, + [name]: checked, + })); + setSelectOriginAll(false); + }; + const handleSelectAllChange = (e) => { const { checked } = e.target; setSelectAll(checked); // Set the 'Select All' state @@ -230,16 +268,42 @@ const DocumentsTab = (props) => { } }; + const handleSelectOriginAllChange = (e) => { + const { checked } = e.target; + setSelectOriginAll(checked); // Set the 'Select All' state + if (checked) { + // If 'Select All' is checked, check all the other checkboxes + + setCheckedOriginItems( + documentOrigin.reduce((acc, item) => { + acc[item.pinswg_origin] = true; + return acc; + }, {}) + ); + } else { + clearCheckboxes(); + } + }; + const clearCheckboxes = () => { setSelectAll(false); + setSelectOriginAll(false); setCheckedItems( documentTypes.reduce((acc, item) => { acc[item.pinswg_isharedocumentlocations] = false; return acc; }, {}), + setSelectedDocumentType("all"), setCurrentPage(1) ); + setCheckedOriginItems( + documentOrigin.reduce((acc, item) => { + acc["origin_" + item.pinswg_origin] = false; + return acc; + }, {}), + setSelectedDocumentOrigin("all") + ); }; let ShowDocLinks = false; let checkShowDocLinks = docsOffline != false ? true : false; @@ -314,6 +378,9 @@ const DocumentsTab = (props) => { {t("search:clear-filter-button-label")} +

+ Document Type +

{" "} <> @@ -335,17 +402,6 @@ const DocumentsTab = (props) => { {router.locale == "cy" ? "Pawb" : "All"}{" "} - - ( - {documentTypes.reduce( - (accumulator, currentItem) => { - return ( - accumulator + - currentItem.count - ); - }, - 0 - )} - )
@@ -389,17 +445,105 @@ const DocumentsTab = (props) => { ); })} {" "} +

+ Document Origin +

+
+ {" "} + <> +
+ + +
+ + {documentOrigin.map((item, key) => { + return ( +
+ + +
+ ); + })} +
{" "}
diff --git a/pages/api/admin/getlatestdocuments_api.js b/pages/api/admin/getlatestdocuments_api.js index c5742dab..36b7117c 100644 --- a/pages/api/admin/getlatestdocuments_api.js +++ b/pages/api/admin/getlatestdocuments_api.js @@ -64,9 +64,11 @@ export default async function ApiProxy(req, res) { var fieldSort = req.query.fieldSort || "desc"; var showNumberOfRecords = req.query.showNumberOfRecords || 10; var documentType = req.query.documentType || "all"; + var documentOrigin = req.query.documentOrigin || "all"; var numberOfWeeks = req.query.numberWeeks || 1; var docTypeQueryString = ""; + var docOriginQueryString = ""; if (documentType != "all") { if (documentType.indexOf(",") >= 0) { @@ -91,6 +93,27 @@ export default async function ApiProxy(req, res) { } } + if (documentOrigin != "all") { + if (documentOrigin.indexOf(",") >= 0) { + const documentOriginArr = documentOrigin.split(","); + + docOriginQueryString = " and ("; + documentOriginArr.forEach(function (item, index) { + console.log(item, index); + + if (item != "all") { + docOriginQueryString += " pinswg_origin eq " + item; + + docOriginQueryString += + index < documentOriginArr.length - 1 ? " or " : ""; + } + }); + docOriginQueryString += " )"; + } else { + docOriginQueryString += " and pinswg_origin eq " + documentOrigin; + } + } + function daysAgoISO(days) { const today = new Date(); const resultDate = new Date(today); @@ -103,6 +126,7 @@ export default async function ApiProxy(req, res) { "pinswg_documents?$select=pinswg_name,createdon,pinswg_publishtoweb,_pinswg_documentids_value,pinswg_isharedocumentlocations,pinswg_isharedocumentreference,pinswg_uploadurl,pinswg_uploadstatus,pinswg_origin&$filter=createdon ge " + daysAgoISO(numberOfWeeks) + docTypeQueryString + + docOriginQueryString + "&$orderby=" + orderby + " " + @@ -115,7 +139,7 @@ export default async function ApiProxy(req, res) { console.log( "\n==========================================\n", - "\nnew docs search ", + "\nnew docs search---- ", "\n\nQuery url: " + queryUrl, "\n\nRelay link: " + WEBAPI_URL + queryUrl + hashAPIPath(queryUrl), "\n==========================================\n" From 0b3cb9778b30d41c40b248df62238129a783a3a2 Mon Sep 17 00:00:00 2001 From: rdbsolutions Date: Tue, 23 Sep 2025 12:45:20 +0100 Subject: [PATCH 06/13] queued downloads and stream not buffer of downloads --- .gitignore | 2 + components/case/documents.js | 96 +++++--- components/utils/downloadmanager.js | 48 ++++ components/utils/downloads.js | 324 +++++++++++++++++++++++++ pages/api/documents/download/[id].js | 295 ++++++++++++++-------- styles/sass/welshgov/_application.scss | 20 ++ 6 files changed, 648 insertions(+), 137 deletions(-) create mode 100644 components/utils/downloadmanager.js create mode 100644 components/utils/downloads.js diff --git a/.gitignore b/.gitignore index 7485fbf5..fe4298be 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,5 @@ public/swagger.json certificates/cert-key.pem certificates/cert.pem gitclean.sh +pages/admin/logcheck.js +pages/admin/logchecker.js diff --git a/components/case/documents.js b/components/case/documents.js index 15af88c4..35f6db4a 100644 --- a/components/case/documents.js +++ b/components/case/documents.js @@ -17,6 +17,8 @@ import { import { setCurrentPage } from "../../store/currentView/action"; import { setDocumentDetails } from "../../store/searchOutput/action"; +import DocumentLink from "../utils/downloads"; +import { useDownloadQueue } from "../utils/downloadmanager"; const DocumentDetails = (props) => { let { t, lang } = useTranslation(); @@ -37,7 +39,7 @@ const DocumentDetails = (props) => { const { locale } = router; var documentDetailsArr = documentDetailsObj || []; - + const { startDownload, getStatus } = useDownloadQueue(3); const [selectedOption, setSelectedOption] = useState(10); const [documentTypes, setDocumentTypes] = useState([{}]); const [selectedDocumentType, setSelectedDocumentType] = useState("all"); @@ -764,41 +766,63 @@ const DocumentDetails = (props) => { )} {!ShowDocLinks ? ( - { - toggleDownLoadLink( - key - ), - sendGAEvent( - "event", - "DownloadedFile", - { - caseReference: - props.caseReference, - filename: - detailsObj.pinswg_name, - } - ); - }} - > - - {t( - "case:documents-name-label" - )} - : - - {_.has( - detailsObj, - "pinswg_name" - ) - ? detailsObj.pinswg_name - : ""} - + <> + + {/* { + toggleDownLoadLink( + key + ), + sendGAEvent( + "event", + "DownloadedFile", + { + caseReference: + props.caseReference, + filename: + detailsObj.pinswg_name, + } + ); + }} + > + + {t( + "case:documents-name-label" + )} + : + + {_.has( + detailsObj, + "pinswg_name" + ) + ? detailsObj.pinswg_name + : ""} + */} + ) : ( <> diff --git a/components/utils/downloadmanager.js b/components/utils/downloadmanager.js new file mode 100644 index 00000000..eb16d0bd --- /dev/null +++ b/components/utils/downloadmanager.js @@ -0,0 +1,48 @@ +import { useState, useCallback } from "react"; + +export function useDownloadQueue(maxConcurrent = 3) { + const [queue, setQueue] = useState([]); // queued tasks + const [activeCount, setActiveCount] = useState(0); + const [statuses, setStatuses] = useState({}); // { id: "idle"|"queued"|"downloading"|"done"|"failed" } + + const scheduleNext = useCallback(() => { + setQueue((queueCurr) => { + if (activeCount >= maxConcurrent || queueCurr.length === 0) + return queueCurr; + + const [task, ...rest] = queueCurr; + + setStatuses((s) => ({ ...s, [task.id]: "downloading" })); + setActiveCount((c) => c + 1); + + // Run the download fully in async IIFE + (async () => { + try { + await task.downloadFn(); // triggers fetch + blob + click + setStatuses((s) => ({ ...s, [task.id]: "done" })); + } catch (err) { + console.error(err); + setStatuses((s) => ({ ...s, [task.id]: "failed" })); + } finally { + setActiveCount((c) => c - 1); + scheduleNext(); // promote next in queue + } + })(); + + return rest; + }); + }, [activeCount, maxConcurrent]); + + const startDownload = useCallback( + (id, downloadFn) => { + setStatuses((s) => ({ ...s, [id]: "queued" })); + setQueue((curr) => [...curr, { id, downloadFn }]); + scheduleNext(); + }, + [scheduleNext] + ); + + const getStatus = useCallback((id) => statuses[id] || "idle", [statuses]); + + return { startDownload, getStatus }; +} diff --git a/components/utils/downloads.js b/components/utils/downloads.js new file mode 100644 index 00000000..7283c030 --- /dev/null +++ b/components/utils/downloads.js @@ -0,0 +1,324 @@ +// // import { useState } from "react"; + +// // export default function DocumentLink({ detailsObj, caseReference }) { +// // const [downloadStatus, setDownloadStatus] = useState(""); +// // const [progress, setProgress] = useState(null); + +// // const handleDownload = async () => { +// // setDownloadStatus("Downloading"); +// // setProgress(0); + +// // try { +// // const res = await fetch(detailsObj.pinswg_hashlink); +// // if (!res.ok) throw new Error("Download failed"); + +// // const contentDisposition = res.headers.get("content-disposition"); +// // const filename = contentDisposition +// // ? contentDisposition.split("filename=")[1] +// // : detailsObj.pinswg_name || "document"; + +// // const contentLength = res.headers.get("content-length"); +// // const totalBytes = contentLength +// // ? parseInt(contentLength, 10) +// // : null; + +// // const reader = res.body.getReader(); +// // let receivedBytes = 0; +// // const chunks = []; + +// // while (true) { +// // const { done, value } = await reader.read(); +// // if (done) break; + +// // chunks.push(value); +// // receivedBytes += value.length; + +// // if (totalBytes) { +// // const percent = Math.round( +// // (receivedBytes / totalBytes) * 100 +// // ); +// // setProgress(percent); +// // } +// // } + +// // // Combine chunks into one blob +// // const blob = new Blob(chunks); +// // const url = window.URL.createObjectURL(blob); + +// // const link = document.createElement("a"); +// // link.href = url; +// // link.download = filename; +// // document.body.appendChild(link); +// // link.click(); +// // document.body.removeChild(link); + +// // setDownloadStatus("Download complete!"); +// // setProgress(100); + +// // // Fire GA event +// // window.gtag?.("event", "DownloadedFile", { +// // caseReference, +// // filename, +// // }); +// // } catch (err) { +// // console.error(err); +// // setDownloadStatus("Download failed"); +// // setProgress(null); +// // } +// // }; + +// // return ( +// // <> +// // {detailsObj.pinswg_publishtoweb ? ( +// // +// // ) : ( +// // <> +// // +// // Document name: +// // +// // {detailsObj.pinswg_name || ""} +// // +// // )} + +// // {downloadStatus && ( +// //

+// // {downloadStatus} +// // {/* {progress !== null && ` (${progress}%)`} */} +// //

+// // )} +// // +// // ); +// // } +// import { useState } from "react"; + +// export default function DocumentLink({ +// id, +// detailsObj, +// caseReference, +// startDownload, +// getStatus, +// }) { +// const [downloadStatus, setDownloadStatus] = useState(""); +// const [progress, setProgress] = useState(null); + +// const downloadFile = async () => { +// setDownloadStatus("Downloading"); +// setProgress(0); + +// try { +// const res = await fetch(detailsObj.pinswg_hashlink); +// if (!res.ok) throw new Error("Download failed"); + +// const contentDisposition = res.headers.get("content-disposition"); +// const filename = contentDisposition +// ? contentDisposition.split("filename=")[1] +// : detailsObj.pinswg_name || "document"; + +// const contentLength = res.headers.get("content-length"); +// const totalBytes = contentLength +// ? parseInt(contentLength, 10) +// : null; + +// const reader = res.body.getReader(); +// let receivedBytes = 0; +// const chunks = []; + +// while (true) { +// const { done, value } = await reader.read(); +// if (done) break; + +// chunks.push(value); +// receivedBytes += value.length; + +// if (totalBytes) { +// const percent = Math.round( +// (receivedBytes / totalBytes) * 100 +// ); +// setProgress(percent); +// } +// } + +// const blob = new Blob(chunks); +// const url = window.URL.createObjectURL(blob); + +// const link = document.createElement("a"); +// link.href = url; +// link.download = filename; +// document.body.appendChild(link); +// link.click(); +// document.body.removeChild(link); + +// setDownloadStatus("Download complete!"); +// setProgress(100); + +// window.gtag?.("event", "DownloadedFile", { +// caseReference, +// filename, +// }); +// } catch (err) { +// console.error(err); +// setDownloadStatus("Download failed"); +// setProgress(null); +// } +// }; + +// const handleClick = () => { +// if (getStatus(id) !== "idle") return; // don't double enqueue +// startDownload(id, downloadFile); +// }; + +// const status = getStatus(id); + +// return ( +// <> +// {detailsObj.pinswg_publishtoweb ? ( +// +// ) : ( +// <> +// +// Document name: +// +// {detailsObj.pinswg_name || ""} +// +// )} + +// {status !== "idle" && ( +//

+// {status === "downloading" && progress !== null +// ? `Downloading (${progress}%)` +// : status === "queued" +// ? "Queued" +// : status === "done" +// ? "Download complete!" +// : status === "failed" +// ? "Download failed" +// : ""} +//

+// )} +// +// ); +// } + +import { useState } from "react"; + +export default function DocumentLink({ + id, + detailsObj, + caseReference, + startDownload, + getStatus, +}) { + const [progress, setProgress] = useState(null); + + const downloadFile = async () => { + setProgress(0); + + const res = await fetch(detailsObj.pinswg_hashlink); + if (!res.ok) throw new Error("Download failed"); + + const contentDisposition = res.headers.get("content-disposition"); + const filename = contentDisposition + ? contentDisposition.split("filename=")[1] + : detailsObj.pinswg_name || "document"; + + const contentLength = res.headers.get("content-length"); + const totalBytes = contentLength ? parseInt(contentLength, 10) : null; + + const reader = res.body.getReader(); + let receivedBytes = 0; + const chunks = []; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + chunks.push(value); + receivedBytes += value.length; + + if (totalBytes) { + const percent = Math.round((receivedBytes / totalBytes) * 100); + setProgress(percent); + } + } + + const blob = new Blob(chunks); + const url = window.URL.createObjectURL(blob); + + const link = document.createElement("a"); + link.href = url; + link.download = filename; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + window.URL.revokeObjectURL(url); + + setProgress(100); + window.gtag?.("event", "DownloadedFile", { caseReference, filename }); + }; + + const handleClick = () => { + if (getStatus(id) !== "idle") return; // avoid double enqueue + startDownload(id, downloadFile); + }; + + const status = getStatus(id); + + return ( +
+ {detailsObj.pinswg_publishtoweb ? ( + + ) : ( + + {detailsObj.pinswg_name || ""} + + )} + + {status !== "idle" && ( +

+ {status === "downloading" && progress !== null + ? `Downloading ` + : status === "queued" + ? "Queued" + : status === "done" + ? "Download complete!" + : status === "failed" + ? "Download failed" + : ""} +

+ )} +
+ ); +} diff --git a/pages/api/documents/download/[id].js b/pages/api/documents/download/[id].js index 044e62b9..cbc8db9c 100644 --- a/pages/api/documents/download/[id].js +++ b/pages/api/documents/download/[id].js @@ -1,110 +1,203 @@ -/** - * @swagger - * /api/documents/download/{id}: - * get: - * tags: - * - Documents - * description: Get document - * parameters: - * - name: id - * in: path - * description: iShare ID - * type: string - * required: true - * default: A41567436 - * - name: hash - * in: query - * description: Hash string - * default: e0a1c9cd2817826a25bca67e60b807eae747cbed769e3ec42cf997541c32be71 - * responses: - * 200: - * description: Success - */ +// /** +// * @swagger +// * /api/documents/download/{id}: +// * get: +// * tags: +// * - Documents +// * description: Get document +// * parameters: +// * - name: id +// * in: path +// * description: iShare ID +// * type: string +// * required: true +// * default: A41567436 +// * - name: hash +// * in: query +// * description: Hash string +// * default: e0a1c9cd2817826a25bca67e60b807eae747cbed769e3ec42cf997541c32be71 +// * responses: +// * 200: +// * description: Success +// */ + +// import axios from "axios"; +// import CryptoJS from "crypto-js"; +// import { getToken, consoleLogger } from "../../../../actions"; + +// const WORDKEY = process.env.HASHKEY; +// const accessTokenEndpoint = process.env.ACCESS_TOKEN_ENDPOINT; +// const tenantId = process.env.TENANT; + +// 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 azureHeadersPaged = (access_token) => { +// return { +// headers: { +// "OData-MaxVersion": "4.0", +// "OData-Version": "4.0", +// "Accept": "application/json;odata.metadata=none", +// "Prefer": +// 'odata.include-annotations="*",return=representation, odata.maxpagesize=10', +// "Content-Type": "application/json", +// "Authorization": "Bearer " + access_token, +// }, +// }; +// }; + +// export default async function ApiProxy(req, res) { +// var token = await getToken(); + +// var docRef = req.query; + +// var queryUrl = "documents/download/" + docRef.id + "?hash=" + docRef.hash; + +// const configDocument = (access_token) => { +// return { +// headers: { +// "OData-MaxVersion": "4.0", +// "OData-Version": "4.0", +// "Accept": "application/json", +// "Prefer": 'odata.include-annotations="*",return=representation', +// "Content-Type": "application/json", +// "Authorization": "Bearer " + access_token, +// }, +// responseType: "arraybuffer", +// }; +// }; + +// return axios +// .get( +// WEBAPI_URL + queryUrl, // + hashAPIPath(queryUrl), +// configDocument(token.access_token) +// ) +// .then((response) => { +// const bytes = response.data.byteLength; +// console.log(bytes); + +// res.setHeader( +// "content-disposition", +// "attachment; filename=" + +// response.headers["content-disposition"].split( +// "filename=" +// )[1] +// ); +// return res.status(200).send(response.data); +// }) +// .then(() => { +// console.log(docRef.id + "has downloaded"); + +// return "downloadComplete"; +// }) +// .catch((error) => { +// consoleLogger(error); +// res.redirect("/filenotavailable"); +// }); +// } import axios from "axios"; -import CryptoJS from "crypto-js"; import { getToken, consoleLogger } from "../../../../actions"; -const WORDKEY = process.env.HASHKEY; -const accessTokenEndpoint = process.env.ACCESS_TOKEN_ENDPOINT; -const tenantId = process.env.TENANT; - 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 azureHeadersPaged = (access_token) => { - return { - headers: { - "OData-MaxVersion": "4.0", - "OData-Version": "4.0", - "Accept": "application/json;odata.metadata=none", - "Prefer": - 'odata.include-annotations="*",return=representation, odata.maxpagesize=10', - "Content-Type": "application/json", - "Authorization": "Bearer " + access_token, - }, - }; -}; - -export default async function ApiProxy(req, res) { - var token = await getToken(); - - var docRef = req.query; - - var queryUrl = "documents/download/" + docRef.id + "?hash=" + docRef.hash; - - const configDocument = (access_token) => { - return { - headers: { - "OData-MaxVersion": "4.0", - "OData-Version": "4.0", - "Accept": "application/json", - "Prefer": 'odata.include-annotations="*",return=representation', - "Content-Type": "application/json", - "Authorization": "Bearer " + access_token, - }, - responseType: "arraybuffer", - }; - }; - - return axios - .get( - WEBAPI_URL + queryUrl, // + hashAPIPath(queryUrl), - configDocument(token.access_token) - ) - .then((response) => { - const bytes = response.data.byteLength; - console.log(bytes); - - res.setHeader( - "content-disposition", - "attachment; filename=" + - response.headers["content-disposition"].split( - "filename=" - )[1] - ); - return res.status(200).send(response.data); - }) - .then(() => { - console.log(docRef.id + "has downloaded"); - - return "downloadComplete"; - }) - .catch((error) => { - consoleLogger(error); - res.redirect("/filenotavailable"); - }); +// Retry utility with logging +async function retry(fn, retries = 3, delay = 1000) { + for (let attempt = 1; attempt <= retries; attempt++) { + try { + return { response: await fn(), attempts: attempt }; + } catch (err) { + if (attempt === retries) throw err; + console.log(`Retry ${attempt} failed, retrying in ${delay}ms...`); + await new Promise((res) => setTimeout(res, delay)); + delay *= 2; // exponential backoff + } + } } + +const ApiProxy = async (req, res) => { + const docRef = req.query; + + try { + const token = await getToken(); + const startTime = Date.now(); + + const fetchStream = async () => { + const queryUrl = + "documents/download/" + docRef.id + "?hash=" + docRef.hash; + + return axios.get(WEBAPI_URL + queryUrl, { + headers: { + "OData-MaxVersion": "4.0", + "OData-Version": "4.0", + "Accept": "application/json", + "Prefer": + 'odata.include-annotations="*",return=representation', + "Content-Type": "application/json", + "Authorization": "Bearer " + token.access_token, + }, + responseType: "stream", + }); + }; + + const { response, attempts } = await retry(fetchStream, 3, 1000); + + // Extract filename + const contentDisposition = response.headers["content-disposition"]; + const filename = contentDisposition + ? contentDisposition.split("filename=")[1] + : docRef.id; + + res.setHeader( + "Content-Disposition", + `attachment; filename=${filename}` + ); + res.setHeader("Content-Type", "application/octet-stream"); + + let totalBytes = 0; + + response.data.on("data", (chunk) => { + totalBytes += chunk.length; + }); + + response.data.pipe(res); + + response.data.on("end", () => { + const durationMs = Date.now() - startTime; + console.log( + `[Download Complete] Document: ${filename}, ID: ${docRef.id}, Size: ${totalBytes} bytes, Duration: ${durationMs}ms, Attempts: ${attempts}` + ); + }); + + response.data.on("error", (err) => { + consoleLogger(err); + if (!res.headersSent) res.redirect("/filenotavailable"); + }); + } catch (error) { + consoleLogger(error); + if (!res.headersSent) res.redirect("/filenotavailable"); + } +}; + +export const config = { + api: { + responseLimit: false, + }, +}; + +export default ApiProxy; diff --git a/styles/sass/welshgov/_application.scss b/styles/sass/welshgov/_application.scss index b80401b7..9e2f3b14 100644 --- a/styles/sass/welshgov/_application.scss +++ b/styles/sass/welshgov/_application.scss @@ -1469,6 +1469,22 @@ textarea, top: calc(50% - 10px); } } + + .documentLink { + border: none; + text-align: left; + cursor: pointer; + color: #0360a6; + display: block; + padding-left: 0px; + + &:hover { + background-color: $white; + color: #3b7dc5; + border: none; + padding-left: 0px + } + } } .appealModule { @@ -2932,6 +2948,10 @@ fade { .govuk-label-s { font-size: 14px; } + + &.govuk-checkboxes_short { + height: 180px; + } } From 118d8a56c7adc5677eef9cf5ec6037fe413870f1 Mon Sep 17 00:00:00 2001 From: rdbsolutions Date: Wed, 1 Oct 2025 09:18:31 +0100 Subject: [PATCH 07/13] welsh language labels --- .gitignore | 1 + components/utils/downloads.js | 11 ++++++----- locales/cy/case.json | 6 +++++- locales/en/case.json | 6 +++++- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index fe4298be..ad55dc2c 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ certificates/cert.pem gitclean.sh pages/admin/logcheck.js pages/admin/logchecker.js +pages/baracuda.html diff --git a/components/utils/downloads.js b/components/utils/downloads.js index 7283c030..6e8c81b4 100644 --- a/components/utils/downloads.js +++ b/components/utils/downloads.js @@ -222,6 +222,7 @@ // } import { useState } from "react"; +import useTranslation from "next-translate/useTranslation"; export default function DocumentLink({ id, @@ -231,7 +232,7 @@ export default function DocumentLink({ getStatus, }) { const [progress, setProgress] = useState(null); - + let { t } = useTranslation(); const downloadFile = async () => { setProgress(0); @@ -309,13 +310,13 @@ export default function DocumentLink({ } > {status === "downloading" && progress !== null - ? `Downloading ` + ? t("case:downloading-label") : status === "queued" - ? "Queued" + ? t("case:download-queued-label") : status === "done" - ? "Download complete!" + ? t("case:download-complete-label") : status === "failed" - ? "Download failed" + ? t("case:download-failed-label") : ""}

)} diff --git a/locales/cy/case.json b/locales/cy/case.json index a0490f05..da629c24 100644 --- a/locales/cy/case.json +++ b/locales/cy/case.json @@ -100,5 +100,9 @@ "do-you-want-to-delete-case-disclaimer-label": "Ar ôl dileu, bydd yr holl wybodaeth a gyflwynwyd gennych yn cael ei cholli.", "representation-date-passed-label": "Mae'r cyfnod cyflwyno sylwadau ar gyfer yr apêl hon rhwng {{startDate}} a {{endDate}}", "representation-date-passed-additional-label": "Gallwch wneud sylw drwy e-bostio,", - "date-raised": "Dyddiad codi" + "date-raised": "Dyddiad codi", + "download-complete-label": "Lawrlwytho wedi'i gwblhau!", + "downloading-label": "Wrthi'n llwytho i lawr", + "download-failed-label": "Methodd y lawrlwythiad", + "download-queued-label": "Wedi'i giwio" } \ No newline at end of file diff --git a/locales/en/case.json b/locales/en/case.json index fac349c1..25e02d0c 100644 --- a/locales/en/case.json +++ b/locales/en/case.json @@ -100,5 +100,9 @@ "do-you-want-to-delete-case-disclaimer-label": "Once deleted all information you have submited, will be lost.", "representation-date-passed-label": "The representation period for this appeal is between {{startDate}} and {{endDate}}", "representation-date-passed-additional-label": "You can make a representation by emailing,", - "date-raised": "Date raised" + "date-raised": "Date raised", + "download-complete-label": "Download complete!", + "downloading-label": "Downloading", + "download-failed-label": "Download failed", + "download-queued-label": "Queued" } \ No newline at end of file From 84aed438f88cf1f71a31aa8e3ec997ade5ff4738 Mon Sep 17 00:00:00 2001 From: rdbsolutions Date: Tue, 28 Oct 2025 11:17:08 +0000 Subject: [PATCH 08/13] 19548 remove setcaseinvolvement --- components/case/summary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/case/summary.js b/components/case/summary.js index 5e340ebd..a01ae25d 100644 --- a/components/case/summary.js +++ b/components/case/summary.js @@ -129,7 +129,7 @@ const CaseSummary = (props) => { }); }); - setCaseInvolvment(incidentid, loggedInUser); + //setCaseInvolvment(incidentid, loggedInUser); }; const isWatchedCase = (whichIncident) => { From d0d2b5d7a97681f7eef558d723e6134e03321201 Mon Sep 17 00:00:00 2001 From: rdbsolutions Date: Tue, 28 Oct 2025 11:18:10 +0000 Subject: [PATCH 09/13] 19845 other party rep dates now showing - added in query --- actions/selectQueryTypes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/selectQueryTypes.js b/actions/selectQueryTypes.js index 936ad6a8..1c4cdb3c 100644 --- a/actions/selectQueryTypes.js +++ b/actions/selectQueryTypes.js @@ -4,7 +4,7 @@ export const getSelectQuery = (appealTypeName) => { return "&$select=pinswg_speacialistcaseprocess,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddresscounty,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_procedure,pinswg_questionnaireduedate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent,pinswg_startdate,pinswg_otherpartiesstatement,pinswg_finalcommentsduedate,pinswg_proofsofevidencewrittenstatementsofeviden,pinswg_speacialistcaseprocess,pinswg_statementduedate"; break; case "pinswg_callinss77s": - return "&$select=modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddresscounty,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_procedure,pinswg_proofsofevidencewrittenstatementsofeviden,pinswg_questionnaireduedate,pinswg_startdate,statuscode,pinswg_dateexportedtoiss,pinswg_startdateofevent,pinswg_typeofevent,pinswg_statementsduedate"; + return "&$select=modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddresscounty,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_procedure,pinswg_proofsofevidencewrittenstatementsofeviden,pinswg_questionnaireduedate,pinswg_startdate,statuscode,pinswg_dateexportedtoiss,pinswg_startdateofevent,pinswg_typeofevent,pinswg_statementsduedate,pinswg_otherpartiesstatement"; break; case "pinswg_commonlands": return "&$select=modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddresscounty,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_proofsofevidencewrittenstatementsofeviden,pinswg_questionnaireduedate,pinswg_relevantauthorityname,pinswg_startdate,statuscode,pinswg_starttimeoftheevent,pinswg_typeofevent,pinswg_statementsduedate"; From df199cff532081f51d5ecf37f014c3f7a2f8ceb5 Mon Sep 17 00:00:00 2001 From: rdbsolutions Date: Tue, 28 Oct 2025 11:20:02 +0000 Subject: [PATCH 10/13] 19889 added in curmurl_version variable --- pages/api/file/createrepinvolvement_api.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pages/api/file/createrepinvolvement_api.js b/pages/api/file/createrepinvolvement_api.js index 545f34ea..cd059683 100644 --- a/pages/api/file/createrepinvolvement_api.js +++ b/pages/api/file/createrepinvolvement_api.js @@ -43,9 +43,11 @@ export default async function ApiProxy(req, res) { console.log(req.body, queryUrl); var crmUrl = "https://" + process.env.CRMURL; + var crmVersion = process.env.CRMURL_VERSION; var data = { - "@odata.id": crmUrl + "/api/data/v8.2/contacts(" + contactid + ")", + "@odata.id": + crmUrl + "/api/data/" + crmVersion + "/contacts(" + contactid + ")", }; var config = { From e54a0b225340cb3bf56bf2b8bd23b1db3afc86d4 Mon Sep 17 00:00:00 2001 From: rdbsolutions Date: Tue, 28 Oct 2025 11:21:38 +0000 Subject: [PATCH 11/13] updated serching to reduce CRM calls for performance and stability --- actions/index.js | 37 ++-- components/admin/tabs/appeals.js | 2 +- components/search/dnssearchresults.js | 21 +- components/search/searchresults.js | 2 +- components/utils/index.js | 179 ++++++++++++++---- pages/advancedsearchresults.js | 6 +- .../getbasicsearchdetailspaged_api.js | 6 +- pages/dnsapplications.js | 57 +++--- pages/myportal/advancedsearchresults.js | 15 +- pages/myportal/dnsapplications.js | 33 ++-- pages/myportal/searchresults.js | 24 +-- pages/searchresults.js | 47 ++--- 12 files changed, 265 insertions(+), 164 deletions(-) diff --git a/actions/index.js b/actions/index.js index fd04e4a8..826c15f0 100644 --- a/actions/index.js +++ b/actions/index.js @@ -532,23 +532,30 @@ export const getBasicSearchDetailsPaged = async ( appealTypeName, caseReference, primaryIdAttribute, - incidentID + incidentIDs ) => { - //console.log("check appealtype:", appealType, caseReference); + if (!incidentIDs || incidentIDs.length === 0) return []; - return axios - .get( - "/api/endpoint/getbasicsearchdetailspaged_api?appealTypeName=" + - appealTypeName + - "&primaryIdAttribute=" + - primaryIdAttribute + - "&incidentID=" + - incidentID - ) - .then((res) => res.data) - .catch((error) => { - consoleLogger(error); - }); + // Build OData filter correctly + const filter = incidentIDs + .map((id) => `_${primaryIdAttribute}s_value eq ${id}`) + .join(" or "); + + const url = `/api/endpoint/getbasicsearchdetailspaged_api?appealTypeName=${appealTypeName}&primaryIdAttribute=${primaryIdAttribute}&incidentID=${encodeURIComponent( + filter + )}`; + + // console.log( + // `Fetching ${incidentIDs.length} incidents for appeal type: ${appealTypeName}` + // ); + + try { + const res = await axios.get(url); + return res.data.value || []; // ensure returning an array + } catch (error) { + consoleLogger(error); + return []; + } }; export const getSearchDocumentDetails = (incidentID) => { diff --git a/components/admin/tabs/appeals.js b/components/admin/tabs/appeals.js index ce39a05f..bfa2115e 100644 --- a/components/admin/tabs/appeals.js +++ b/components/admin/tabs/appeals.js @@ -221,7 +221,7 @@ function AppealsTab(props) { resultsArr.map((item, key) => { let detailsObj = jsonpath({ path: - '$..value[?(@ && @.ticketnumber=="' + + '$..[?(@ && @.ticketnumber=="' + item.ticketnumber + '")]', json: searchDetailsObj, diff --git a/components/search/dnssearchresults.js b/components/search/dnssearchresults.js index 14c9f218..2e385fda 100644 --- a/components/search/dnssearchresults.js +++ b/components/search/dnssearchresults.js @@ -303,7 +303,7 @@ const DNSSearchResults = (props) => { resultsArr.map((item, key) => { let detailsObj = jsonpath({ path: - '$..value[?(@ && @.ticketnumber=="' + + '$..[?(@ && @.ticketnumber=="' + item.ticketnumber + '")]', json: searchDetailsObj, @@ -798,7 +798,14 @@ const DNSSearchResults = (props) => { )} - {resultsArr.length > 0 ? ( + + {!searchLoaded ? ( +
+
+

Loading...

+
+
+ ) : resultsArr.length > 0 ? ( ResultsView(resultsArr) ) : (
@@ -807,6 +814,16 @@ const DNSSearchResults = (props) => {
)} + + {/* {resultsArr.length > 0 ? ( + ResultsView(resultsArr) + ) : ( +
+
+

{t("search:searchresults-no-records-label")}

+
+
+ )} */} ); }; diff --git a/components/search/searchresults.js b/components/search/searchresults.js index 2656f674..70483a40 100644 --- a/components/search/searchresults.js +++ b/components/search/searchresults.js @@ -244,7 +244,7 @@ const SearchResults = (props) => { resultsArr.map((item, key) => { let detailsObj = jsonpath({ path: - '$..value[?(@ && @.ticketnumber=="' + + '$..[?(@ && @.ticketnumber=="' + item.ticketnumber + '")]', json: searchDetailsObj, diff --git a/components/utils/index.js b/components/utils/index.js index eaef542b..07831bcb 100644 --- a/components/utils/index.js +++ b/components/utils/index.js @@ -179,49 +179,146 @@ export const getPartSavedDetails = (searchResultsObj) => { * Get the details of a case from the appeal type * @param object searchResultsObj */ -export const getSearchDetailsPaged = (searchResultsObj) => { - let detailsArr = []; - searchResultsObj = searchResultsObj.value; - const detailsObj = searchResultsObj.map((searchDetail, index) => { - if (searchDetail.pinswg_appealcasetype == null) { - console.log(searchDetail.title); - } else { - let formMeta = getFormCollectionByID( - searchDetail.pinswg_appealcasetype - ); - formMeta?.LogicalCollectionName && - formMeta?.LogicalCollectionName != "pinswg_sipses" && - detailsArr.push( - getBasicSearchDetailsPaged( - formMeta.LogicalCollectionName, - searchDetail.title, - formMeta.PrimaryIdAttribute, - searchDetail.incidentid - ) - ); - } - }); - return Promise.all(detailsArr); -}; - -// export const absoluteUrl = (req, setLocalhost) => { -// var protocol = "https:"; -// var host = req -// ? req.headers["x-forwarded-host"] || req.headers["host"] -// : window.location.host; - -// if (host.indexOf("localhost") > -1) { -// if (setLocalhost) host = setLocalhost; -// protocol = "http:"; -// } - -// return { -// protocol: protocol, -// host: host, -// origin: protocol + "//" + host, -// }; +// export const getSearchDetailsPaged = (searchResultsObj) => { +// let detailsArr = []; +// searchResultsObj = searchResultsObj.value; +// const detailsObj = searchResultsObj.map((searchDetail, index) => { +// if (searchDetail.pinswg_appealcasetype == null) { +// console.log(searchDetail.title); +// } else { +// let formMeta = getFormCollectionByID( +// searchDetail.pinswg_appealcasetype +// ); +// formMeta?.LogicalCollectionName && +// formMeta?.LogicalCollectionName != "pinswg_sipses" && +// detailsArr.push( +// getBasicSearchDetailsPaged( +// formMeta.LogicalCollectionName, +// searchDetail.title, +// formMeta.PrimaryIdAttribute, +// searchDetail.incidentid +// ) +// ); +// } +// }); +// return Promise.all(detailsArr); // }; +// export const getSearchDetailsPaged = async (searchResultsObj) => { +// const searchResults = searchResultsObj.value; + +// //Group incidents by appeal type +// const groupedByAppealType = {}; + +// searchResults.forEach((incident) => { +// const appealType = incident.pinswg_appealcasetype; +// if (!appealType) return; + +// const formMeta = getFormCollectionByID(appealType); +// if ( +// !formMeta?.LogicalCollectionName || +// formMeta.LogicalCollectionName === "pinswg_sipses" +// ) +// return; + +// const key = formMeta.LogicalCollectionName; +// if (!groupedByAppealType[key]) groupedByAppealType[key] = []; +// groupedByAppealType[key].push({ +// incidentID: incident.incidentid, +// caseReference: incident.title, +// primaryIdAttribute: formMeta.PrimaryIdAttribute, +// }); +// }); + +// // Make all API calls in parallel per appeal type +// const allDetails = await Promise.all( +// Object.entries(groupedByAppealType).map( +// async ([appealTypeName, incidents]) => { +// console.log( +// `Fetching details for appeal type: ${appealTypeName}, incidents: ${incidents.length}` +// ); + +// // Fully parallel for each incident +// const results = await Promise.all( +// incidents.map((i) => +// getBasicSearchDetailsPaged( +// appealTypeName, +// i.caseReference, +// i.primaryIdAttribute, +// i.incidentID +// ).catch((err) => { +// console.error( +// `Error fetching incident ${i.caseReference}:`, +// err +// ); +// return null; // continue other calls even if one fails +// }) +// ) +// ); + +// return results.filter((r) => r); // remove nulls from failed calls +// } +// ) +// ); + +// //Flatten all results into a single array +// return allDetails.flat(); +// }; + +export const getSearchDetailsPaged = async (searchResultsObj) => { + searchResultsObj = searchResultsObj.value; + + // Group incidents by appeal type + const groupedByAppealType = searchResultsObj.reduce((acc, detail) => { + const appealType = detail.pinswg_appealcasetype; + if (!appealType) { + console.log("No appeal type for:", detail.title); + return acc; + } + + if (!acc[appealType]) acc[appealType] = []; + acc[appealType].push({ + incidentID: detail.incidentid, + title: detail.title, + }); + return acc; + }, {}); + + const allDetails = []; + + // For each appeal type, call getBasicSearchDetailsPaged once with all incident IDs + for (const [appealType, incidents] of Object.entries(groupedByAppealType)) { + const incidentIDs = incidents.map((i) => i.incidentID); + + // Get the form meta for this appeal type to access primaryIdAttribute + const formMeta = getFormCollectionByID(appealType); + + if (!formMeta || !formMeta.PrimaryIdAttribute) { + console.log(`Missing form metadata for appeal type: ${appealType}`); + continue; + } + + // console.log( + // `Fetching ${incidentIDs.length} incidents for appeal type: ${appealType} with primaryIdAttribute: ${formMeta.PrimaryIdAttribute}` + // ); + + try { + const details = await getBasicSearchDetailsPaged( + formMeta.LogicalCollectionName, // appealTypeName + null, // caseReference is no longer used in batch + formMeta.PrimaryIdAttribute, // primaryIdAttribute + incidentIDs // pass array of incidentIDs + ); + + allDetails.push(...details); + } catch (err) { + consoleLogger(err); + } + } + + return allDetails; +}; + export const getProgressObj = ( formObjXML, titleList, diff --git a/pages/advancedsearchresults.js b/pages/advancedsearchresults.js index 05848822..f2135ea9 100644 --- a/pages/advancedsearchresults.js +++ b/pages/advancedsearchresults.js @@ -124,8 +124,8 @@ export const getServerSideProps = wrapper.getServerSideProps( // "=============================== Advanced Search results:", // searchResultsObj // ); - const searchDetailsObj = - (await getSearchDetails(searchResultsObj)) || null; + // const searchDetailsObj = + // (await getSearchDetails(searchResultsObj)) || null; // console.log( // "=============================== Advanced Search results details:", // searchDetailsObj @@ -136,7 +136,7 @@ export const getServerSideProps = wrapper.getServerSideProps( store.dispatch(setShowReps(showReps, showLoginCheck)); store.dispatch(setSearchResults(searchResultsObj)); - store.dispatch(setSearchDetails(searchDetailsObj)); + // store.dispatch(setSearchDetails(searchDetailsObj)); store.dispatch(setSearch(Object.entries(query))); return { props: { diff --git a/pages/api/endpoint/getbasicsearchdetailspaged_api.js b/pages/api/endpoint/getbasicsearchdetailspaged_api.js index b2acaa78..08977c5c 100644 --- a/pages/api/endpoint/getbasicsearchdetailspaged_api.js +++ b/pages/api/endpoint/getbasicsearchdetailspaged_api.js @@ -66,9 +66,7 @@ export default async function ApiProxy(req, res) { var queryUrl = appealTypeName + - "?$filter=_" + - primaryIdAttribute + - "s_value eq " + + "?$filter=" + incidentID + "&$count=true" + "&$expand=" + @@ -79,7 +77,7 @@ export default async function ApiProxy(req, res) { queryUrl = queryUrl + getSelectQuery(appealTypeName); - //console.log("query: ", queryUrl, "<<< Date: Mon, 10 Nov 2025 06:22:31 +0000 Subject: [PATCH 12/13] docoffline dependant appeals and reps --- components/case/summary.js | 135 +++++++++++++++++++------- components/myportal.js | 6 +- components/myportal/makenewappeal.js | 65 +++++++++++-- components/search/dnssearchresults.js | 2 +- components/search/searchresults.js | 2 +- locales/cy/case.json | 3 +- locales/cy/myportal.json | 3 +- locales/en/case.json | 3 +- locales/en/myportal.json | 3 +- pages/myportal/index.js | 3 + 10 files changed, 178 insertions(+), 47 deletions(-) diff --git a/components/case/summary.js b/components/case/summary.js index a01ae25d..7f6dbc94 100644 --- a/components/case/summary.js +++ b/components/case/summary.js @@ -111,6 +111,38 @@ const CaseSummary = (props) => { ? true : false; + let ShowDocLinks = false; + let checkShowDocLinks = docsOffline != false ? true : false; + + const getDocLink = (docsOffline) => { + var startTime = docsOffline.split(",")[0]; + var endTime = docsOffline.split(",")[1]; + + // console.log(startTime); + // console.log(endTime); + + var currentDate = new Date(); + + var startDate = new Date(currentDate.getTime()); + startDate.setHours(startTime.split(":")[0]); + startDate.setMinutes(startTime.split(":")[1]); + startDate.setSeconds(startTime.split(":")[2]); + + var endDate = new Date(currentDate.getTime()); + endDate.setHours(endTime.split(":")[0]); + endDate.setMinutes(endTime.split(":")[1]); + endDate.setSeconds(endTime.split(":")[2]); + + // console.log(startDate); + // console.log(endDate); + // console.log(currentDate); + + var valid = startDate < currentDate && endDate > currentDate; + ShowDocLinks = valid; + }; + + checkShowDocLinks && getDocLink(docsOffline); + const selectWatchedCase = (loggedInUser, incidentID, appealType) => { let updateBody = { "pinswg_WatchedCase@odata.bind": "/incidents(" + incidentID + ")", @@ -468,31 +500,51 @@ const CaseSummary = (props) => { .appealType ) ? ( <> - - {t( - "case:summary-make-representation-label" - )} - - + {ShowDocLinks ? ( +
+ + + + Notice + + {t( + "case:docsoffline-reps-label" + )} + +
+ ) : ( +
+ + {t( + "case:summary-make-representation-label" + )} + +
+ )}
{ //spinnerState(); @@ -802,15 +854,32 @@ const CaseSummary = (props) => { router.pathname ) == true ? ( "" + ) : ShowDocLinks ? ( +
+ + + + Notice + + {t("case:docsoffline-reps-label")} + +
) : ( - - {t( - "case:summary-make-representation-label" - )} - +
+ + {t( + "case:summary-make-representation-label" + )} + +
)}
{ - const { showFileUpload, showLoginCheck } = props; + const { showFileUpload, showLoginCheck, docsOffline } = props; let { t } = useTranslation(); const router = useRouter(); @@ -126,7 +126,9 @@ const MyPortal = (props) => {
- {!isLPA && } + {!isLPA && ( + + )} {/* {props.awaitingSubmission.awaitingSubmission[ "@odata.count" diff --git a/components/myportal/makenewappeal.js b/components/myportal/makenewappeal.js index 789be38f..f06b01b5 100644 --- a/components/myportal/makenewappeal.js +++ b/components/myportal/makenewappeal.js @@ -2,11 +2,46 @@ import useTranslation from "next-translate/useTranslation"; import Link from "next/link"; import { useRouter } from "next/router"; -export default function MakeNewAppeal() { +export default function MakeNewAppeal(props) { let { t } = useTranslation(); + const { docsOffline } = props; + const router = useRouter(); const { locale } = router; + + let ShowDocLinks = false; + let checkShowDocLinks = docsOffline != false ? true : false; + + const getDocLink = (docsOffline) => { + var startTime = docsOffline.split(",")[0]; + var endTime = docsOffline.split(",")[1]; + + // console.log(startTime); + // console.log(endTime); + + var currentDate = new Date(); + + var startDate = new Date(currentDate.getTime()); + startDate.setHours(startTime.split(":")[0]); + startDate.setMinutes(startTime.split(":")[1]); + startDate.setSeconds(startTime.split(":")[2]); + + var endDate = new Date(currentDate.getTime()); + endDate.setHours(endTime.split(":")[0]); + endDate.setMinutes(endTime.split(":")[1]); + endDate.setSeconds(endTime.split(":")[2]); + + // console.log(startDate); + // console.log(endDate); + // console.log(currentDate); + + var valid = startDate < currentDate && endDate > currentDate; + ShowDocLinks = valid; + }; + + console.log("docsOffline", docsOffline, getDocLink(docsOffline)); + return (
@@ -22,11 +57,29 @@ export default function MakeNewAppeal() {

{t("myportal:makenewappeal-card-paragraph-two")}

-
- - {t("myportal:makenewappeal-card-button-label")} - -
+ {ShowDocLinks ? ( +
+ + + + Notice + + {t("case:docsoffline-reps-label")} + +
+ ) : ( +
+ + {t("myportal:makenewappeal-card-button-label")} + +
+ )} +

{t("myportal:makenewappeal-card-paragraph-three")}

diff --git a/components/search/dnssearchresults.js b/components/search/dnssearchresults.js index 2e385fda..2f67478a 100644 --- a/components/search/dnssearchresults.js +++ b/components/search/dnssearchresults.js @@ -147,7 +147,7 @@ const DNSSearchResults = (props) => { }; const [orderByState, setOrderbyState] = useState("createdon"); - const [fieldSortState, setfieldSortState] = useState("asc"); + const [fieldSortState, setfieldSortState] = useState("desc"); const [selectedOption, setSelectedOption] = useState(10); const cookies = parseCookies(); diff --git a/components/search/searchresults.js b/components/search/searchresults.js index 70483a40..d50f1226 100644 --- a/components/search/searchresults.js +++ b/components/search/searchresults.js @@ -66,7 +66,7 @@ const SearchResults = (props) => { const [showSpinnerState, setShowSpinnerState] = useState(false); const [selectedOption, setSelectedOption] = useState(10); const [orderByState, setOrderbyState] = useState("createdon"); - const [fieldSortState, setfieldSortState] = useState("asc"); + const [fieldSortState, setfieldSortState] = useState("desc"); const [loginToWatch, setLoginToWatch] = useState("true"); let spinnerState = () => { diff --git a/locales/cy/case.json b/locales/cy/case.json index da629c24..c39412c1 100644 --- a/locales/cy/case.json +++ b/locales/cy/case.json @@ -104,5 +104,6 @@ "download-complete-label": "Lawrlwytho wedi'i gwblhau!", "downloading-label": "Wrthi'n llwytho i lawr", "download-failed-label": "Methodd y lawrlwythiad", - "download-queued-label": "Wedi'i giwio" + "download-queued-label": "Wedi'i giwio", + "docsoffline-reps-label": "Oherwydd cynnal a chadw'r system, ni allwn dderbyn sylwadau ar hyn o bryd. Rhowch gynnig arall arni yn nes ymlaen." } \ No newline at end of file diff --git a/locales/cy/myportal.json b/locales/cy/myportal.json index dd3254c6..bf8e2e4b 100644 --- a/locales/cy/myportal.json +++ b/locales/cy/myportal.json @@ -51,5 +51,6 @@ "date-raised": "Dyddiad codi", "representation-submitted": "Cyflwynwyd y sylw", "submission-processing-label": "Prosesu cyflwyniad", - "appeal-pending-label": "Yn yr arfaeth" + "appeal-pending-label": "Yn yr arfaeth", + "docsoffline-newappeal-label": "Oherwydd gwaith cynnal a chadw'r system, ni allwn dderbyn apeliadau newydd ar hyn o bryd. Rhowch gynnig arall arni yn nes ymlaen." } \ No newline at end of file diff --git a/locales/en/case.json b/locales/en/case.json index 25e02d0c..3630c75e 100644 --- a/locales/en/case.json +++ b/locales/en/case.json @@ -104,5 +104,6 @@ "download-complete-label": "Download complete!", "downloading-label": "Downloading", "download-failed-label": "Download failed", - "download-queued-label": "Queued" + "download-queued-label": "Queued", + "docsoffline-reps-label": "Due to system maintenance, we are unable to accept representations at this time. Please try again later." } \ No newline at end of file diff --git a/locales/en/myportal.json b/locales/en/myportal.json index 9a9fe897..1208d8d7 100644 --- a/locales/en/myportal.json +++ b/locales/en/myportal.json @@ -51,5 +51,6 @@ "date-raised": "Date raised", "representation-submitted": "Representation submitted", "submission-processing-label": "Submission processing", - "appeal-pending-label": "Pending" + "appeal-pending-label": "Pending", + "docsoffline-newappeal-label": "Due to system maintenance, we are unable to accept new appeals at this time. Please try again later." } \ No newline at end of file diff --git a/pages/myportal/index.js b/pages/myportal/index.js index 3c0e05de..a103df47 100644 --- a/pages/myportal/index.js +++ b/pages/myportal/index.js @@ -66,6 +66,7 @@ const Home = (props) => { showLoginCheck, currentView, mySubmittedReps, + docsOffline, } = props; let { t, lang } = useTranslation(); @@ -161,6 +162,7 @@ const Home = (props) => { containerID={accountDetails.containerID} showLoginCheck={showLoginCheck} currentView={currentView} + docsOffline={docsOffline} />
@@ -366,6 +368,7 @@ export const getServerSideProps = wrapper.getServerSideProps( showFileUpload: process.env.SHOWFILEUPLOAD, containerID: thisSession.user.id, showLoginCheck: process.env.SHOWLOGIN, + docsOffline: process.env.DOCAPI_OFFLINE || false, }, }; } From 6a7806f5ea644dbf2d7197d83013e434dfbfed89 Mon Sep 17 00:00:00 2001 From: rdbsolutions Date: Tue, 18 Nov 2025 07:57:09 +0000 Subject: [PATCH 13/13] fixed ui misaligned button and wrong text --- components/case/summary.js | 50 +++++++++++++--------------- components/myportal/makenewappeal.js | 2 +- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/components/case/summary.js b/components/case/summary.js index 7f6dbc94..8b50b04e 100644 --- a/components/case/summary.js +++ b/components/case/summary.js @@ -518,32 +518,30 @@ const CaseSummary = (props) => {
) : ( -
- - {t( - "case:summary-make-representation-label" - )} - -
+ + {t( + "case:summary-make-representation-label" + )} + )}
{ diff --git a/components/myportal/makenewappeal.js b/components/myportal/makenewappeal.js index f06b01b5..ee1ac54a 100644 --- a/components/myportal/makenewappeal.js +++ b/components/myportal/makenewappeal.js @@ -69,7 +69,7 @@ export default function MakeNewAppeal(props) { Notice - {t("case:docsoffline-reps-label")} + {t("myportal:docsoffline-newappeal-label")}
) : (