Merged PR 2009: partial synch with ph2 updates

partial synch with ph2 updates

Related work items: #20553
This commit is contained in:
Robert Bond
2025-11-28 08:50:01 +00:00
34 changed files with 1425 additions and 471 deletions
+2
View File
@@ -26,6 +26,8 @@ export default function AccountsTab({ userData }) {
>
{user.email}
</Link>
<br />
{user.id}
</dd>
<dd className="govuk-summary-list__value govuk-!-font-size-16 ">
Last accessed: {user.created}
+1 -1
View File
@@ -221,7 +221,7 @@ function AppealsTab(props) {
resultsArr.map((item, key) => {
let detailsObj = jsonpath({
path:
'$..value[?(@ && @.ticketnumber=="' +
'$..[?(@ && @.ticketnumber=="' +
item.ticketnumber +
'")]',
json: searchDetailsObj,
+216 -49
View File
@@ -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")}
</button>
</div>
<h4 className="govuk-body govuk-!-font-size-14 govuk-!-margin-bottom-0">
Document Type
</h4>
<div className="govuk-checkboxes govuk-checkboxes--small">
{" "}
<>
@@ -335,17 +402,6 @@ const DocumentsTab = (props) => {
{router.locale == "cy"
? "Pawb"
: "All"}{" "}
- (
{documentTypes.reduce(
(accumulator, currentItem) => {
return (
accumulator +
currentItem.count
);
},
0
)}
)
</label>
</div>
</>
@@ -389,17 +445,105 @@ const DocumentsTab = (props) => {
);
})}
</div>{" "}
<h4 className="govuk-body govuk-!-font-size-14 govuk-!-margin-bottom-0">
Document Origin
</h4>
<div className="govuk-checkboxes govuk-checkboxes_short govuk-checkboxes--small">
{" "}
<>
<div className="govuk-checkboxes__item">
<input
className="govuk-checkboxes__input"
type="checkbox"
id={"all_" + "_" + 0}
key={0}
name={"all_" + "_" + 0}
checked={selectOriginAll}
value={"all"}
onChange={
handleSelectOriginAllChange
}
/>
<label
className="govuk-label-s govuk-checkboxes__label"
htmlFor={"all" + "__" + 0}
>
{router.locale == "cy"
? "Pawb"
: "All"}{" "}
</label>
</div>
</>
{documentOrigin.map((item, key) => {
return (
<div
className="govuk-checkboxes__item"
key={key}
>
<input
className="govuk-checkboxes__input"
type="checkbox"
id={"origin_" + item.Value}
key={key}
value={"origin_" + item.Value}
name={"origin_" + item.Value}
checked={
checkedOriginItems[
"origin_" + item.Value
]
}
onChange={
handleCheckboxOriginChange
}
/>
<label
className="govuk-label-s govuk-checkboxes__label"
htmlFor={"origin_" + item.Value}
>
{router.locale == "cy"
? jsonpath({
path:
'$..[?(@ && @.value=="' +
item.Label +
'")].value_cy',
json: transLookup,
eval: true,
})
: item.Label ||
t(
"case:summary-no-date-entered-label"
)}{" "}
</label>
</div>
);
})}
</div>{" "}
<div>
<button
className="govuk-button "
type="button"
onClick={(e) => {
const data = [
const checkedValues = [
...document.querySelectorAll(
".govuk-checkboxes__input:checked"
),
].map((e) => e.value);
setSelectedDocumentType(data);
const origins = checkedValues
.filter((val) =>
val.startsWith("origin_")
)
.map((val) =>
val.replace(/^origin_/, "")
); // strip prefix
const types = checkedValues.filter(
(val) => !val.startsWith("origin_")
);
setSelectedDocumentType(types);
setSelectedDocumentOrigin(origins);
setCurrentPage(1);
}}
>
@@ -610,43 +754,63 @@ const DocumentsTab = (props) => {
{!ShowDocLinks ? (
detailsObj.pinswg_publishtoweb ? (
<Link
scroll={
false
// <Link
// scroll={
// false
// }
// href={
// detailsObj.pinswg_hashlink
// }
// key={key}
// onClick={() => {
// toggleDownLoadLink(
// key
// ),
// sendGAEvent(
// "event",
// "DownloadedFile",
// {
// caseReference:
// props.caseReference,
// filename:
// detailsObj.pinswg_name,
// }
// );
// }}
// >
// <span className="results-visually-hidden">
// {t(
// "case:documents-name-label"
// )}
// :
// </span>
// {_.has(
// detailsObj,
// "pinswg_name"
// )
// ? detailsObj.pinswg_name
// : ""}
// </Link>
<DocumentLink
key={
detailsObj.pinswg_documentid
}
href={
detailsObj.pinswg_hashlink
id={
detailsObj.pinswg_documentid
}
key={key}
onClick={() => {
toggleDownLoadLink(
key
),
sendGAEvent(
"event",
"DownloadedFile",
{
caseReference:
props.caseReference,
filename:
detailsObj.pinswg_name,
}
);
}}
>
<span className="results-visually-hidden">
{t(
"case:documents-name-label"
)}
:
</span>
{_.has(
detailsObj,
"pinswg_name"
)
? detailsObj.pinswg_name
: ""}
</Link>
detailsObj={
detailsObj
}
caseReference={
caseReference
}
startDownload={
startDownload
}
getStatus={
getStatus
}
/>
) : (
<>
<span className="results-visually-hidden">
@@ -797,6 +961,7 @@ const DocumentsTab = (props) => {
fieldSort,
selectedOption,
selectedDocumentType,
selectedDocumentOrigin,
selectedWeeks
)
.then((data) => data)
@@ -810,6 +975,7 @@ const DocumentsTab = (props) => {
selectedOption,
selectedDocumentType,
selectedWeeks,
selectedDocumentOrigin,
props.setDocumentDetails,
]
);
@@ -942,6 +1108,7 @@ const DocumentsTab = (props) => {
fieldSortState,
orderByState,
getDocumentTypes,
getOrigin,
getDocumentResults,
lang,
selectedWeeks,
+76 -85
View File
@@ -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");
@@ -287,12 +289,9 @@ const DocumentDetails = (props) => {
{/* {t("case:documents-date-published-label")} */}
<button
type="button"
onClick={() =>
sortDataBy("pinswg_documentpublisheddate")
}
onClick={() => sortDataBy("createdon")}
className={
orderByState ==
"pinswg_documentpublisheddate"
orderByState == "createdon"
? fieldSortState == "asc"
? `govuk-link govuk-link--no-underline govuk-!-font-weight-bold govuk-!-font-size-16 sortAsc`
: `govuk-link govuk-link--no-underline govuk-!-font-weight-bold govuk-!-font-size-16 sortDesc`
@@ -700,14 +699,9 @@ const DocumentDetails = (props) => {
{/* {t("case:documents-date-published-label")} */}
<button
type="button"
onClick={() =>
sortDataBy(
"pinswg_documentpublisheddate"
)
}
onClick={() => sortDataBy("createdon")}
className={
orderByState ==
"pinswg_documentpublisheddate"
orderByState == "createdon"
? fieldSortState == "asc"
? `govuk-link govuk-link--no-underline govuk-!-font-weight-bold govuk-!-font-size-16 sortAsc`
: `govuk-link govuk-link--no-underline govuk-!-font-weight-bold govuk-!-font-size-16 sortDesc`
@@ -772,41 +766,63 @@ const DocumentDetails = (props) => {
)}
{!ShowDocLinks ? (
<Link
scroll={false}
href={
detailsObj.pinswg_hashlink
}
key={key}
onClick={() => {
toggleDownLoadLink(
key
),
sendGAEvent(
"event",
"DownloadedFile",
{
caseReference:
props.caseReference,
filename:
detailsObj.pinswg_name,
}
);
}}
>
<span className="results-visually-hidden">
{t(
"case:documents-name-label"
)}
:
</span>
{_.has(
detailsObj,
"pinswg_name"
)
? detailsObj.pinswg_name
: ""}
</Link>
<>
<DocumentLink
key={
detailsObj.pinswg_documentid
}
id={
detailsObj.pinswg_documentid
}
detailsObj={
detailsObj
}
caseReference={
caseReference
}
startDownload={
startDownload
}
getStatus={
getStatus
}
/>
{/* <Link
scroll={false}
href={
detailsObj.pinswg_hashlink
}
key={key}
onClick={() => {
toggleDownLoadLink(
key
),
sendGAEvent(
"event",
"DownloadedFile",
{
caseReference:
props.caseReference,
filename:
detailsObj.pinswg_name,
}
);
}}
>
<span className="results-visually-hidden">
{t(
"case:documents-name-label"
)}
:
</span>
{_.has(
detailsObj,
"pinswg_name"
)
? detailsObj.pinswg_name
: ""}
</Link> */}
</>
) : (
<>
<span className="results-visually-hidden">
@@ -858,36 +874,14 @@ const DocumentDetails = (props) => {
:
</span>
{/* {
detailsObj.pinswg_documentpublisheddate
}
{
detailsObj.pinswg_latestpublisheddate
} */}
{detailsObj.pinswg_documentpublisheddate !=
null
? formatDates(
detailsObj.pinswg_documentpublisheddate
)
: formatDates(
detailsObj.pinswg_latestpublisheddate
)}
{/* {_.has(
{_.has(
detailsObj,
"pinswg_documentpublisheddate"
"pinswg_latestpublisheddate"
)
? formatDates(
detailsObj.pinswg_documentpublisheddate
)
: _.has(
detailsObj,
"pinswg_latestpublisheddate"
)
? formatDates(
detailsObj.pinswg_latestpublisheddate
)
: ""} */}
: ""}
</dd>
{/* <dd className="govuk-summary-list__value govuk-!-font-size-16">
<span className="results-visually-hidden">
@@ -931,9 +925,8 @@ const DocumentDetails = (props) => {
selectedOption,
selectedDocumentType
)
// .then((data) => data)
.then((data) => data)
.then((data) => {
setDocumentSearchState(true);
setDocumentDetails(data);
setShowSpinnerState(false);
});
@@ -1003,10 +996,8 @@ const DocumentDetails = (props) => {
};
const [showDocumentSearchState, setDocumentSearchState] = useState(false);
const [orderByState, setOrderbyState] = useState(
"pinswg_documentpublisheddate"
);
const [fieldSortState, setfieldSortState] = useState("desc");
const [orderByState, setOrderbyState] = useState("createdon");
const [fieldSortState, setfieldSortState] = useState("asc");
let documentSearchState = () => {
showDocumentSearchState == true
@@ -1016,14 +1007,14 @@ const DocumentDetails = (props) => {
const sortDataBy = (whichField) => {
setOrderbyState(whichField);
setfieldSortState("desc");
setfieldSortState("asc");
getDocumentResults(1, whichField, fieldSortState);
whichField == orderByState
? fieldSortState == "asc"
? setfieldSortState("desc")
: setfieldSortState("asc")
: setfieldSortState("desc");
? fieldSortState == "desc"
? setfieldSortState("asc")
: setfieldSortState("desc")
: setfieldSortState("asc");
setCurrentPage(1);
setShowSpinnerState(true);
};
@@ -1058,7 +1049,7 @@ const DocumentDetails = (props) => {
return docObj;
};
//const searchDocumentResultsObj = docDetailsObj();
const searchDocumentResultsObj = docDetailsObj();
}, [
incidentid,
setDocumentDetails,
+80 -4
View File
@@ -57,6 +57,7 @@ const CaseSummary = (props) => {
const router = useRouter();
const { locale } = router;
const {
appealTypeID,
incidentid,
caseReference,
myCases,
@@ -79,7 +80,6 @@ const CaseSummary = (props) => {
ticketnumber,
} = props;
let appealTypeID = props.appealTypeID || router.query.appealType;
const showLoginCheck =
currentType == "directResultsObj" ? "true" : currentView.showLogin;
const { data: session, status } = useSession();
@@ -482,14 +482,19 @@ const CaseSummary = (props) => {
<div className="govuk-summary-list__row">
<dt className="govuk-summary-list__key">
{getFormCollectionByID(
appealTypeID
appealTypeID ||
setCaseQueryObj.value[0]
.pinswg_appealcasetype
).PrimaryIdAttribute ==
"pinswg_dnsid"
? t(
"case:summary-applicantonly-label"
)
: getFormCollectionByID(
appealTypeID
appealTypeID ||
setCaseQueryObj
.value[0]
.pinswg_appealcasetype
).PrimaryIdAttribute ==
"pinswg_nonvalidationid"
? t(
@@ -542,7 +547,9 @@ const CaseSummary = (props) => {
</dt>
<dd className="govuk-summary-list__value">
{getFormCollectionByID(
appealTypeID
appealTypeID ||
setCaseQueryObj.value[0]
.pinswg_appealcasetype
).PrimaryIdAttribute ==
"pinswg_dnsid" ||
getFormCollectionByID(
@@ -1980,6 +1987,67 @@ const CaseSummary = (props) => {
expDate.setDate(now.getDate() + 365);
function formatDates(dateObj, showTime) {
var dateObj = new Date(dateObj);
var dd = String(dateObj.getDate()).padStart(2, "0");
var mm = String(dateObj.getMonth() + 1).padStart(2, "0"); //January is 0!
var yyyy = dateObj.getFullYear();
var hh = dateObj.getHours();
hh = ("0" + hh).slice(-2);
var mins = dateObj.getMinutes();
mins = ("0" + mins).slice(-2);
const dateStr = showTime
? hh == 0 && mins == 0
? ""
: hh + ":" + mins
: dd + "/" + mm + "/" + yyyy + " ";
return dateStr;
}
function linkedCasesList(linkedCasesReference) {
//console.log(linkedCasesReference);
linkedCasesReference = linkedCasesReference.value || [];
return (
<>
<ul className="govuk-list govuk-!-font-size-16">
{linkedCasesReference.map((item, key) => {
return (
<li key={key}>
<Link
href={
router.locale == "cy"
? "/canlyniadauchwilio?q=" +
item.title +
"&lk=1"
: "/searchresults?q=" +
item.title +
"&lk=1"
}
className=""
>
{item.title}
</Link>
</li>
);
//console.log(item.incidentid, item.title);
})}
</ul>
</>
);
}
function showReps(startDate, endDate) {
let date = new Date();
date = new Date(date.toDateString());
const start = new Date(startDate);
const end = new Date(endDate);
return date >= start && date <= end ? true : false;
}
function showRepButton(appealType) {
switch (appealType) {
case 846040012:
@@ -2027,6 +2095,14 @@ const CaseSummary = (props) => {
}
}
function showRepsEnded(startDate, endDate) {
let date = new Date();
date = new Date(date.toDateString());
const start = new Date(startDate);
const end = new Date(endDate);
return date > start && date > end ? true : false;
}
if (status == "authenticated" && cookies.pedwWatchCase != null) {
selectWatchedCase(
props.accountDetails.accountDetails.contactid,
+1 -6
View File
@@ -748,7 +748,7 @@ const RenderDatePicker = ({
: value
: null
} //{parseISO(value) || null}
className="govuk-input govuk-input--width-12"
className="govuk-input govuk-input--width-10"
minDate={minDate}
maxDate={maxDate}
autoComplete="off"
@@ -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"],
+4 -2
View File
@@ -19,7 +19,7 @@ import { JSONPath as jsonpath } from "jsonpath-plus";
import transLookup from "../data/lookuptranslations.json";
const MyPortal = (props) => {
const { showFileUpload, showLoginCheck } = props;
const { showFileUpload, showLoginCheck, docsOffline } = props;
let { t } = useTranslation();
const router = useRouter();
@@ -126,7 +126,9 @@ const MyPortal = (props) => {
<div className="govuk-grid-row">
<div className="govuk-grid-column-full">
<div className="flex-container grid-row govuk-body ">
{!isLPA && <MakeNewAppeal />}
{!isLPA && (
<MakeNewAppeal docsOffline={docsOffline} />
)}
<SearchCases />
{/* {props.awaitingSubmission.awaitingSubmission[
"@odata.count"
+1 -1
View File
@@ -34,7 +34,7 @@ const AwaitingSubmission = (props) => {
<div className="cardVieAll">
<Link
href={
(router.locale != "en"
router.locale != "en"
? "/" +
router.locale +
"/fymhorth/gweldpopeth?key=awaitingSubmission"
+59 -6
View File
@@ -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 (
<div className="card" id="new-appeal-card">
<div className="card-body">
@@ -22,11 +57,29 @@ export default function MakeNewAppeal() {
<p className="govuk-body-s">
{t("myportal:makenewappeal-card-paragraph-two")}
</p>
<div>
<Link href={"/newappeal"} className="govuk-button">
{t("myportal:makenewappeal-card-button-label")}
</Link>
</div>
{ShowDocLinks ? (
<div className="govuk-warning-text ">
<span
className="govuk-warning-text__icon"
aria-hidden="true"
>
!
</span>
<strong className="govuk-warning-text__text govuk-!-margin-top-2">
<span className="govuk-visually-hidden">
Notice
</span>
{t("myportal:docsoffline-newappeal-label")}
</strong>
</div>
) : (
<div>
<Link href={"/newappeal"} className="govuk-button">
{t("myportal:makenewappeal-card-button-label")}
</Link>
</div>
)}
<p className="govuk-body-s govuk govuk-!-margin-top-5">
{t("myportal:makenewappeal-card-paragraph-three")}
</p>
@@ -33,7 +33,7 @@ const MyRepresentations = (props) => {
<div className="cardVieAll">
<Link
href={
(router.locale != "en"
router.locale != "en"
? "/" +
router.locale +
"/fymhorth/gweldpopeth?key=myRepresentations"
+1 -1
View File
@@ -85,7 +85,7 @@ const ServiceBanner = (props) => {
<div className="serviceBanner_userDetails_name">
<Link
href="/account/personaldetails"
className="govuk-button-secondary govuk-link--no-underline"
className="govuk-button-secondary govuk-link"
>
{props.accountDetails.accountDetails.firstname}{" "}
{props.accountDetails.accountDetails.lastname}
+1 -1
View File
@@ -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 = () => {
+48
View File
@@ -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 + <a> 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 };
}
+325
View File
@@ -0,0 +1,325 @@
// // 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 ? (
// // <button className="documentLink" onClick={handleDownload}>
// // <span className="results-visually-hidden">
// // Document name:
// // </span>
// // {detailsObj.pinswg_name || ""}
// // </button>
// // ) : (
// // <>
// // <span className="results-visually-hidden">
// // Document name:
// // </span>
// // {detailsObj.pinswg_name || ""}
// // </>
// // )}
// // {downloadStatus && (
// // <p
// // className={
// // downloadStatus == "Downloading"
// // ? "govuk-!-font-size-14 govuk-!-margin-left-0 textloading"
// // : "govuk-!-font-size-14 govuk-!-margin-left-0 "
// // }
// // >
// // {downloadStatus}
// // {/* {progress !== null && ` (${progress}%)`} */}
// // </p>
// // )}
// // </>
// // );
// // }
// 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 ? (
// <button className="documentLink" onClick={handleClick}>
// <span className="results-visually-hidden">
// Document name:
// </span>
// {detailsObj.pinswg_name || ""}
// </button>
// ) : (
// <>
// <span className="results-visually-hidden">
// Document name:
// </span>
// {detailsObj.pinswg_name || ""}
// </>
// )}
// {status !== "idle" && (
// <p
// className={
// status === "downloading"
// ? "govuk-!-font-size-14 govuk-!-margin-left-0 textloading"
// : "govuk-!-font-size-14 govuk-!-margin-left-0"
// }
// >
// {status === "downloading" && progress !== null
// ? `Downloading (${progress}%)`
// : status === "queued"
// ? "Queued"
// : status === "done"
// ? "Download complete!"
// : status === "failed"
// ? "Download failed"
// : ""}
// </p>
// )}
// </>
// );
// }
import { useState } from "react";
import useTranslation from "next-translate/useTranslation";
export default function DocumentLink({
id,
detailsObj,
caseReference,
startDownload,
getStatus,
}) {
const [progress, setProgress] = useState(null);
let { t } = useTranslation();
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 (
<div>
{detailsObj.pinswg_publishtoweb ? (
<button className="documentLink" onClick={handleClick}>
<span className="results-visually-hidden">
Document name:
</span>
{detailsObj.pinswg_name || ""}
</button>
) : (
<span className="results-visually-hidden">
{detailsObj.pinswg_name || ""}
</span>
)}
{status !== "idle" && (
<p
className={
status === "downloading"
? "govuk-!-font-size-14 govuk-!-margin-left-0 textloading"
: "govuk-!-font-size-14 govuk-!-margin-left-0"
}
>
{status === "downloading" && progress !== null
? t("case:downloading-label")
: status === "queued"
? t("case:download-queued-label")
: status === "done"
? t("case:download-complete-label")
: status === "failed"
? t("case:download-failed-label")
: ""}
</p>
)}
</div>
);
}
+50 -21
View File
@@ -179,29 +179,58 @@ export const getPartSavedDetails = (searchResultsObj) => {
* Get the details of a case from the appeal type
* @param object searchResultsObj
*/
export const getSearchDetailsPaged = (searchResultsObj) => {
let detailsArr = [];
export const getSearchDetailsPaged = async (searchResultsObj) => {
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
)
);
// 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;
}
});
return Promise.all(detailsArr);
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 absoluteUrl = (req, setLocalhost) => {