partial synch with ph2 updates
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
// components/admin/tabs/AccountsTab.js
|
||||
import Link from "next/link";
|
||||
|
||||
export default function AccountsTab({ userData }) {
|
||||
return (
|
||||
<div>
|
||||
<h1>User Accounts</h1>
|
||||
<p>Number of accounts created: {userData.length}</p>
|
||||
|
||||
<dl className="govuk-summary-list govuk-!-margin-bottom-9 results">
|
||||
<div className="govuk-summary-list__row results-headings">
|
||||
<dd className="govuk-summary-list__key govuk-!-font-size-16">
|
||||
Name
|
||||
</dd>
|
||||
<dd className="govuk-summary-list__key govuk-!-font-size-16">
|
||||
Last accessed
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
{userData.map((user) => (
|
||||
<div key={user.id} className="govuk-summary-list__row">
|
||||
<dd className="govuk-summary-list__value govuk-!-font-size-16 ">
|
||||
<Link
|
||||
target="_blank"
|
||||
href={`/auth/signin2?email=${user.email}`}
|
||||
>
|
||||
{user.email}
|
||||
</Link>
|
||||
<br />
|
||||
{user.id}
|
||||
</dd>
|
||||
<dd className="govuk-summary-list__value govuk-!-font-size-16 ">
|
||||
Last accessed: {user.created}
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,568 @@
|
||||
// components/admin/tabs/AppealsTab.js
|
||||
import useTranslation from "next-translate/useTranslation";
|
||||
import { useCallback, useState, useEffect } from "react";
|
||||
import { JSONPath as jsonpath } from "jsonpath-plus";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import PaginationControl from "../../../components/search/pagination";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import {
|
||||
setSearchResults,
|
||||
setSearchDetails,
|
||||
} from "../../../store/searchOutput/action";
|
||||
|
||||
import { getDetailsProxy, getSearchDetailsPaged } from "../../utils";
|
||||
import { getAdvancedSearchPaged, getNewAppealsPage } from "../../../actions";
|
||||
import {
|
||||
setCurrentPage,
|
||||
setCurrentReference,
|
||||
} from "../../../store/currentView/action";
|
||||
|
||||
function AppealsTab(props) {
|
||||
const [searchLoaded, setSearchLoaded] = useState(false);
|
||||
const [showSpinnerState, setShowSpinnerState] = useState(false);
|
||||
const [selectedOption, setSelectedOption] = useState(10);
|
||||
const [orderByState, setOrderbyState] = useState("createdon");
|
||||
const [fieldSortState, setfieldSortState] = useState("desc");
|
||||
const [loginToWatch, setLoginToWatch] = useState("true");
|
||||
|
||||
const searchDetailsObj = props.searchResultsObj.searchDetailsObj || {};
|
||||
const resultsArr = props.searchResultsObj.searchResultsObj.value || [];
|
||||
const searchResultsObj = props.searchResultsObj.searchResultsObj;
|
||||
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const myportal = false;
|
||||
const advancedSearch = false;
|
||||
const searchString = "";
|
||||
|
||||
const spinnerState = () => {
|
||||
setShowSpinnerState((prev) => !prev);
|
||||
};
|
||||
|
||||
const pagesCount = Math.ceil(searchResultsObj["@odata.count"] / 10);
|
||||
|
||||
let currentPage = 0;
|
||||
try {
|
||||
currentPage = decodeURI(searchResultsObj["@odata.nextLink"]).split(
|
||||
"skiptoken="
|
||||
)[1];
|
||||
currentPage =
|
||||
typeof currentPage !== "undefined"
|
||||
? parseInt(
|
||||
decodeURI(currentPage)
|
||||
.split('pagenumber="')[1]
|
||||
.slice(
|
||||
0,
|
||||
decodeURI(currentPage)
|
||||
.split('pagenumber="')[1]
|
||||
.indexOf('"')
|
||||
) - 1
|
||||
)
|
||||
: pagesCount;
|
||||
} catch (e) {
|
||||
currentPage = pagesCount;
|
||||
}
|
||||
|
||||
const getSearchPageResults = useCallback(
|
||||
(pageNumber, orderBy, fieldSort) => {
|
||||
getNewAppealsPage(
|
||||
searchString,
|
||||
pageNumber,
|
||||
orderBy,
|
||||
fieldSort,
|
||||
selectedOption
|
||||
).then((data) => {
|
||||
props.setSearchResults(data); // ✅ use props, not import
|
||||
return getSearchDetailsPaged(data).then((details) => {
|
||||
props.setSearchDetails(details); // ✅ use props, not import
|
||||
setShowSpinnerState(false);
|
||||
});
|
||||
});
|
||||
},
|
||||
[
|
||||
advancedSearch,
|
||||
searchString,
|
||||
selectedOption,
|
||||
props.setSearchDetails,
|
||||
props.setSearchResults,
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (searchLoaded) {
|
||||
if (selectedOption) {
|
||||
setShowSpinnerState(true);
|
||||
getSearchPageResults(1, orderByState, fieldSortState);
|
||||
}
|
||||
} else {
|
||||
setSearchLoaded(true);
|
||||
}
|
||||
}, [
|
||||
selectedOption,
|
||||
fieldSortState,
|
||||
orderByState,
|
||||
searchLoaded,
|
||||
getSearchPageResults,
|
||||
router.query,
|
||||
]);
|
||||
|
||||
const ResultsView = (resultsArr) => {
|
||||
return (
|
||||
<>
|
||||
<dl className="govuk-summary-list govuk-!-margin-bottom-9 results">
|
||||
<div className="govuk-summary-list__row results-headings">
|
||||
<dd className="govuk-summary-list__key govuk-!-font-size-16 results_wider ">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => sortDataBy("title")}
|
||||
className={
|
||||
orderByState == "title"
|
||||
? 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`
|
||||
: `govuk-link govuk-link--no-underline govuk-!-font-weight-bold govuk-!-font-size-16`
|
||||
}
|
||||
>
|
||||
{t("search:searchresults-case-reference-label")}
|
||||
</button>
|
||||
</dd>
|
||||
<dd className="govuk-summary-list__key govuk-!-font-size-16">
|
||||
{t("search:searchresults-site-address-label")}
|
||||
</dd>
|
||||
<dd className="govuk-summary-list__key govuk-!-font-size-16">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => sortDataBy("_customerid_value")}
|
||||
className={
|
||||
orderByState == "_customerid_value"
|
||||
? 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`
|
||||
: `govuk-link govuk-link--no-underline govuk-!-font-weight-bold govuk-!-font-size-16`
|
||||
}
|
||||
>
|
||||
{t("search:searchresults-applicant-label")}
|
||||
</button>
|
||||
</dd>
|
||||
<dd className="govuk-summary-list__key govuk-!-font-size-16">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
sortDataBy("_pinswg_associatedlpa_value")
|
||||
}
|
||||
className={
|
||||
orderByState ==
|
||||
"_pinswg_associatedlpa_value"
|
||||
? 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`
|
||||
: `govuk-link govuk-link--no-underline govuk-!-font-weight-bold govuk-!-font-size-16`
|
||||
}
|
||||
>
|
||||
{t("search:searchresults-authority-label")}
|
||||
</button>
|
||||
</dd>
|
||||
<dd className="govuk-summary-list__key govuk-!-font-size-16">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
sortDataBy("pinswg_appealcasetype")
|
||||
}
|
||||
className={
|
||||
orderByState == "pinswg_appealcasetype"
|
||||
? 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`
|
||||
: `govuk-link govuk-link--no-underline govuk-!-font-weight-bold govuk-!-font-size-16`
|
||||
}
|
||||
>
|
||||
{t("search:searchresults-case-type-label")}
|
||||
</button>
|
||||
</dd>
|
||||
<dd className="govuk-summary-list__key govuk-!-font-size-16">
|
||||
{/* {t("search:searchresults-status-label")} */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => sortDataBy("statuscode")}
|
||||
className={
|
||||
orderByState == "statuscode"
|
||||
? 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`
|
||||
: `govuk-link govuk-link--no-underline govuk-!-font-weight-bold govuk-!-font-size-16`
|
||||
}
|
||||
>
|
||||
{t("search:searchresults-status-label")}
|
||||
</button>
|
||||
</dd>
|
||||
|
||||
<dd className="govuk-summary-list__key govuk-!-font-size-16 govuk-summary-list__action">
|
||||
Origin
|
||||
</dd>
|
||||
</div>
|
||||
{showSpinnerState == true ? (
|
||||
<div className="govuk-summary-list__row">
|
||||
<div className="govuk-!-margin-top-9 govuk-!-margin-bottom-9 govuk-!-font-weight-bold govuk-!-font-size-20 ">
|
||||
{t("common:spinner-fetching-data")}
|
||||
<img
|
||||
className="data-loading-icon"
|
||||
src="/assets/images/loading.gif"
|
||||
alt={
|
||||
router.locale == "cy"
|
||||
? "Nôl data"
|
||||
: "Fetching data"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
resultsArr.map((item, key) => {
|
||||
let detailsObj = jsonpath({
|
||||
path:
|
||||
'$..[?(@ && @.ticketnumber=="' +
|
||||
item.ticketnumber +
|
||||
'")]',
|
||||
json: searchDetailsObj,
|
||||
eval: true,
|
||||
});
|
||||
|
||||
detailsObj = detailsObj[0] || {};
|
||||
return (
|
||||
<div
|
||||
className="govuk-summary-list__row"
|
||||
key={key}
|
||||
>
|
||||
<dd className="govuk-summary-list__value govuk-!-font-size-16 ">
|
||||
<div></div>
|
||||
{item.pinswg_publishtoweb == true ? (
|
||||
<Link
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={
|
||||
(router.locale == "cy"
|
||||
? myportal == true
|
||||
? "/fymhorth/achos"
|
||||
: "/achos"
|
||||
: myportal == true
|
||||
? "/myportal/case"
|
||||
: "/case") +
|
||||
"/" +
|
||||
item.ticketnumber
|
||||
}
|
||||
className="govuk-link--no-underline"
|
||||
onClick={() => {
|
||||
setCurrentReference({
|
||||
"ticketnumber":
|
||||
item.ticketnumber,
|
||||
"currentReference":
|
||||
item.title,
|
||||
"currentType":
|
||||
"searchResultsObj",
|
||||
"incidentid":
|
||||
item.incidentid,
|
||||
"appealType":
|
||||
item.pinswg_appealcasetype,
|
||||
"showLoginCheck":
|
||||
props.showLoginCheck,
|
||||
"specialistProcess":
|
||||
detailsObj.hasOwnProperty(
|
||||
"pinswg_speacialistcaseprocess"
|
||||
)
|
||||
? detailsObj.pinswg_speacialistcaseprocess
|
||||
: detailsObj.hasOwnProperty(
|
||||
"pinswg_specialistcaseprocess"
|
||||
)
|
||||
? detailsObj.pinswg_specialistcaseprocess
|
||||
: "",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span className="results-visually-hidden">
|
||||
{t(
|
||||
"search:searchresults-case-reference-label"
|
||||
)}
|
||||
:
|
||||
</span>
|
||||
{item.title}
|
||||
</Link>
|
||||
) : (
|
||||
item.title
|
||||
)}
|
||||
|
||||
<br />
|
||||
{new Date(
|
||||
item.createdon
|
||||
).toLocaleString()}
|
||||
</dd>
|
||||
<dd className="govuk-summary-list__value govuk-!-font-size-16">
|
||||
<span className="results-visually-hidden">
|
||||
{t(
|
||||
"search:searchresults-site-address-label"
|
||||
)}
|
||||
:
|
||||
</span>
|
||||
{_.has(
|
||||
detailsObj,
|
||||
"pinswg_projectlocation"
|
||||
)
|
||||
? detailsObj.pinswg_projectlocation !=
|
||||
null
|
||||
? detailsObj.pinswg_projectlocation.indexOf(
|
||||
";"
|
||||
) < 0
|
||||
? detailsObj.pinswg_projectlocation
|
||||
: router.locale == "cy"
|
||||
? detailsObj.pinswg_projectlocation.split(
|
||||
";"
|
||||
)[1]
|
||||
: detailsObj.pinswg_projectlocation.split(
|
||||
";"
|
||||
)[0]
|
||||
: ""
|
||||
: ""}
|
||||
{_.has(
|
||||
detailsObj,
|
||||
"pinswg_siteaddressline1"
|
||||
)
|
||||
? detailsObj.pinswg_siteaddressline1
|
||||
: ""}
|
||||
|
||||
{/* {detailsObj.pinswg_siteaddressline1 !=
|
||||
null && <br />} */}
|
||||
|
||||
{_.has(
|
||||
detailsObj,
|
||||
"pinswg_siteaddressline1"
|
||||
) &&
|
||||
detailsObj.pinswg_siteaddressline1 !=
|
||||
null && <br />}
|
||||
|
||||
{_.has(
|
||||
detailsObj,
|
||||
"pinswg_siteaddressline1"
|
||||
)
|
||||
? detailsObj.pinswg_siteaddressline2
|
||||
: ""}
|
||||
|
||||
{_.has(
|
||||
detailsObj,
|
||||
"pinswg_siteaddressline2"
|
||||
) &&
|
||||
detailsObj.pinswg_siteaddressline2 !=
|
||||
null && <br />}
|
||||
{_.has(
|
||||
detailsObj,
|
||||
"pinswg_siteaddresstown"
|
||||
)
|
||||
? detailsObj.pinswg_siteaddresstown
|
||||
: ""}
|
||||
{_.has(
|
||||
detailsObj,
|
||||
"pinswg_siteaddresstown"
|
||||
) &&
|
||||
detailsObj.pinswg_siteaddresstown !=
|
||||
null && <br />}
|
||||
|
||||
{_.has(
|
||||
detailsObj,
|
||||
"pinswg_siteaddresscounty"
|
||||
)
|
||||
? detailsObj.pinswg_siteaddresscounty
|
||||
: ""}
|
||||
{_.has(
|
||||
detailsObj,
|
||||
"pinswg_siteaddresscounty"
|
||||
) &&
|
||||
detailsObj.pinswg_siteaddresscounty !=
|
||||
null && <br />}
|
||||
{_.has(
|
||||
detailsObj,
|
||||
"pinswg_siteaddresspostcode"
|
||||
)
|
||||
? detailsObj.pinswg_siteaddresspostcode
|
||||
: ""}
|
||||
</dd>
|
||||
<dd className="govuk-summary-list__value govuk-!-font-size-16">
|
||||
<span className="results-visually-hidden">
|
||||
{t(
|
||||
"search:searchresults-applicant-label"
|
||||
)}
|
||||
:
|
||||
</span>
|
||||
{item.pinswg_appellantagent == 846040001
|
||||
? item.pinswg_appellantfirstname +
|
||||
" " +
|
||||
item.pinswg_appellantlastname
|
||||
: _.has(detailsObj, [
|
||||
"_pinswg_appellant_value@OData.Community.Display.V1.FormattedValue",
|
||||
])
|
||||
? detailsObj[
|
||||
"_pinswg_appellant_value@OData.Community.Display.V1.FormattedValue"
|
||||
]
|
||||
: ""}
|
||||
</dd>
|
||||
<dd className="govuk-summary-list__value govuk-!-font-size-16">
|
||||
<span className="results-visually-hidden">
|
||||
{t(
|
||||
"search:searchresults-authority-label"
|
||||
)}
|
||||
:
|
||||
</span>
|
||||
{_.has(item, [
|
||||
"_pinswg_associatedlpa_value@OData.Community.Display.V1.FormattedValue",
|
||||
])
|
||||
? router.locale == "cy"
|
||||
? jsonpath({
|
||||
path:
|
||||
'$..[?(@ && @.value=="' +
|
||||
item[
|
||||
"_pinswg_associatedlpa_value@OData.Community.Display.V1.FormattedValue"
|
||||
] +
|
||||
'")].value_cy',
|
||||
json: transLookup,
|
||||
eval: true,
|
||||
})
|
||||
: item[
|
||||
"_pinswg_associatedlpa_value@OData.Community.Display.V1.FormattedValue"
|
||||
] || "N/A"
|
||||
: ""}
|
||||
</dd>
|
||||
<dd className="govuk-summary-list__value govuk-!-font-size-16">
|
||||
<span className="results-visually-hidden">
|
||||
{t(
|
||||
"search:searchresults-case-type-label"
|
||||
)}
|
||||
:
|
||||
</span>
|
||||
{router.locale == "cy"
|
||||
? jsonpath({
|
||||
path:
|
||||
'$..[?(@ && @.value=="' +
|
||||
item[
|
||||
"pinswg_appealcasetype@OData.Community.Display.V1.FormattedValue"
|
||||
] +
|
||||
'")].value_cy',
|
||||
json: transLookup,
|
||||
eval: true,
|
||||
})
|
||||
: item[
|
||||
"pinswg_appealcasetype@OData.Community.Display.V1.FormattedValue"
|
||||
] || "N/A"}
|
||||
</dd>
|
||||
<dd className="govuk-summary-list__value govuk-!-font-size-16">
|
||||
<span className="results-visually-hidden">
|
||||
{t(
|
||||
"search:searchresults-status-label"
|
||||
)}
|
||||
:
|
||||
</span>
|
||||
|
||||
{router.locale == "cy"
|
||||
? jsonpath({
|
||||
path:
|
||||
'$..[?(@ && @.value=="' +
|
||||
item[
|
||||
"statuscode@OData.Community.Display.V1.FormattedValue"
|
||||
] +
|
||||
'")].value_cy',
|
||||
json: transLookup,
|
||||
eval: true,
|
||||
})
|
||||
: item[
|
||||
"statuscode@OData.Community.Display.V1.FormattedValue"
|
||||
] || "N/A"}
|
||||
</dd>
|
||||
<dd className="govuk-summary-list__value govuk-!-font-size-16">
|
||||
{item[
|
||||
"caseorigincode@OData.Community.Display.V1.FormattedValue"
|
||||
] || "N/A"}
|
||||
</dd>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</dl>
|
||||
<PaginationControl
|
||||
showNoOfRecords={selectedOption}
|
||||
currentPage={currentPage}
|
||||
searchResultsObj={props.searchResultsObj.searchResultsObj}
|
||||
orderBy={orderByState}
|
||||
fieldSort={fieldSortState}
|
||||
spinnerState={spinnerState}
|
||||
getSearchPageResults={getSearchPageResults}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Appeals</h1>
|
||||
{searchResultsObj["@odata.count"] > 5 && (
|
||||
<div id="recordCountSelect" className="">
|
||||
<div className="govuk-form-group">
|
||||
<label
|
||||
className="govuk-label-s "
|
||||
htmlFor="showNumberOfRecords"
|
||||
>
|
||||
{t("search:searchresults-show-records-label-a")}
|
||||
<select
|
||||
onChange={(e) => {
|
||||
setSelectedOption(e.target.value);
|
||||
props.setCurrentPage(1);
|
||||
}}
|
||||
value={selectedOption}
|
||||
className="govuk-select"
|
||||
id="showNumberOfRecords"
|
||||
name="showNumberOfRecords"
|
||||
>
|
||||
<option value="5">5</option>
|
||||
<option value="10">10</option>
|
||||
<option value="20">20</option>
|
||||
<option value="30">30</option>
|
||||
<option value="50">50</option>
|
||||
</select>{" "}
|
||||
{t("search:searchresults-show-records-label-b")}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{resultsArr.length > 0 ? (
|
||||
ResultsView(resultsArr)
|
||||
) : (
|
||||
<div className="govuk-grid-row">
|
||||
<div className="govuk-grid-column-two-thirds">
|
||||
<h3>{t("search:searchresults-no-records-label")}</h3>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
setCurrentReference: (currentReference) => {
|
||||
dispatch(setCurrentReference(currentReference));
|
||||
},
|
||||
setSearchResults: (searchResultsObj) => {
|
||||
dispatch(setSearchResults(searchResultsObj));
|
||||
},
|
||||
setSearchDetails: (searchDetailsObj) => {
|
||||
dispatch(setSearchDetails(searchDetailsObj));
|
||||
},
|
||||
setCurrentPage: (currentPage) => {
|
||||
dispatch(setCurrentPage(currentPage));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
searchResultsObj: state.searchResultsObj,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AppealsTab);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,266 @@
|
||||
// components/admin/tabs/StorageTab.js
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { bytesToSize } from "../../../components/utils";
|
||||
import {
|
||||
deleteMyRepresentationsFromBlob,
|
||||
deleteAwaitingSubmissionsFromBlob,
|
||||
sendRepCompleteMessage,
|
||||
} from "../../../actions";
|
||||
|
||||
import { formatBlobDates } from "../utils/adminHelper";
|
||||
|
||||
export default function StorageTab({ data, repCompleteCount }) {
|
||||
const [showRepJson, setShowRepJson] = useState(true);
|
||||
const [showTmpFile, setShowTmpFile] = useState(true);
|
||||
const [listData, setListData] = useState(data);
|
||||
|
||||
const toggleAll = () => {
|
||||
const newState = !(showRepJson || showTmpFile);
|
||||
setShowRepJson(newState);
|
||||
setShowTmpFile(newState);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Storage Account Contents</h1>
|
||||
{repCompleteCount > 0 && (
|
||||
<p>Number of Orphaned Reps: {repCompleteCount}</p>
|
||||
)}
|
||||
|
||||
{/* Toggle buttons */}
|
||||
<div style={{ marginBottom: "0.2rem" }}>
|
||||
<button
|
||||
className="govuk-button govuk-button--secondary"
|
||||
onClick={() => setShowRepJson(!showRepJson)}
|
||||
>
|
||||
{showRepJson ? "Hide Reps" : "Show Reps"}
|
||||
</button>
|
||||
<button
|
||||
className="govuk-button govuk-button--secondary"
|
||||
onClick={() => setShowTmpFile(!showTmpFile)}
|
||||
style={{ marginLeft: "0.5rem" }}
|
||||
>
|
||||
{showTmpFile ? "Hide Temp Appeal" : "Show Temp Appeal"}
|
||||
</button>
|
||||
<button
|
||||
className="govuk-button govuk-button--secondary"
|
||||
onClick={toggleAll}
|
||||
style={{ marginLeft: "0.5rem" }}
|
||||
>
|
||||
Show/Hide All
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Render users & containers */}
|
||||
{listData.map((user) => (
|
||||
<div key={user.id} className="govuk-grid-row">
|
||||
{user.containers.map((c) => (
|
||||
<div key={c.container} className="card">
|
||||
<div className="card-body">
|
||||
<h3>
|
||||
{c.container} -{" "}
|
||||
<Link
|
||||
href={`/auth/signin2?email=${user.email}`}
|
||||
>
|
||||
{user.email}
|
||||
</Link>
|
||||
</h3>
|
||||
{/* folders */}
|
||||
{c.folders.map((folder) => (
|
||||
<div
|
||||
key={folder.folderPath}
|
||||
style={
|
||||
showRepJson && folder.hasRepJson
|
||||
? { display: "block" }
|
||||
: showTmpFile &&
|
||||
folder.hasTmpFile
|
||||
? { display: "block" }
|
||||
: { display: "none" }
|
||||
}
|
||||
>
|
||||
<h4>{folder.folderPath || "Root"}</h4>
|
||||
|
||||
{/* Buttons for TMP + Rep actions go here (same logic as before) */}
|
||||
|
||||
{folder.hasTmpFile && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => {
|
||||
confirm(
|
||||
"Do you want to remove this unsubmitted appeal " +
|
||||
folder.folderPath +
|
||||
"?\n"
|
||||
) &&
|
||||
(deleteCaseItem(
|
||||
c.container,
|
||||
folder.folderPath,
|
||||
setData
|
||||
),
|
||||
alert(
|
||||
"Temp case Deleted!"
|
||||
));
|
||||
}}
|
||||
>
|
||||
Delete this TMP appeal
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{folder.hasRepJson && (
|
||||
<div>
|
||||
{" "}
|
||||
<button
|
||||
title={
|
||||
"Do you want to delete rep"
|
||||
}
|
||||
className=" cardModuleRemoveCase govuk-link govuk-link--no-underline govuk-link--inverse "
|
||||
onClick={() => {
|
||||
confirm(
|
||||
"Do you want tyo delete rep " +
|
||||
folder.repJsonBlob.name.split(
|
||||
"/"
|
||||
)[0] +
|
||||
"?\n"
|
||||
) &&
|
||||
(deleteRepItem(
|
||||
c.container,
|
||||
folder.repJsonBlob.name.split(
|
||||
"/"
|
||||
)[0],
|
||||
folder.repJsonBlob.name.split(
|
||||
"/"
|
||||
)[1]
|
||||
),
|
||||
alert(
|
||||
"Rep Deleted!"
|
||||
),
|
||||
window.location.reload());
|
||||
}}
|
||||
>
|
||||
Delete Rep
|
||||
</button>
|
||||
{folder.repComplete && (
|
||||
<button
|
||||
onClick={async () => {
|
||||
const response =
|
||||
await fetch(
|
||||
"/api/file/editRepJson",
|
||||
{
|
||||
method: "POST",
|
||||
headers:
|
||||
{
|
||||
"Content-Type":
|
||||
"application/json",
|
||||
},
|
||||
body: JSON.stringify(
|
||||
{
|
||||
container:
|
||||
c.container,
|
||||
blobName:
|
||||
folder
|
||||
.repJsonBlob
|
||||
.name,
|
||||
}
|
||||
),
|
||||
}
|
||||
);
|
||||
const result =
|
||||
await response.json();
|
||||
if (response.ok) {
|
||||
alert(
|
||||
"repComplete removed!"
|
||||
),
|
||||
window.location.reload();
|
||||
// Optionally refresh the page or update UI state here
|
||||
} else {
|
||||
alert(
|
||||
"Error: " +
|
||||
result.error
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Remove repComplete
|
||||
</button>
|
||||
)}
|
||||
{folder.repComplete && (
|
||||
<button
|
||||
onClick={async () => {
|
||||
confirm(
|
||||
"Do you want resend the rep queue message " +
|
||||
folder.repJsonBlob.name.split(
|
||||
"/"
|
||||
)[0] +
|
||||
" --- " +
|
||||
folder.repJsonBlob.name.split(
|
||||
"/"
|
||||
)[1]
|
||||
) &&
|
||||
(sendRepCompleteMessage(
|
||||
c.container,
|
||||
folder.repJsonBlob.name.split(
|
||||
"/"
|
||||
)[0],
|
||||
folder.repJsonBlob.name.split(
|
||||
"/"
|
||||
)[1]
|
||||
),
|
||||
alert(
|
||||
"rep queue message sent!"
|
||||
),
|
||||
window.location.reload());
|
||||
}}
|
||||
>
|
||||
Recreate Queue Message
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<ul>
|
||||
{folder.blobs.map((blob) => (
|
||||
<li key={blob.name}>
|
||||
<Link
|
||||
scroll={false}
|
||||
href={`/api/file/downloadblob?container=${
|
||||
c.container
|
||||
}&casefolderID=${
|
||||
folder.folderPath
|
||||
}&blobname=${blob.name.substring(
|
||||
blob.name.lastIndexOf(
|
||||
"/"
|
||||
) + 1
|
||||
)}&hash=${
|
||||
blob.hashedfilepath
|
||||
}`}
|
||||
className="govuk-body govuk-!-font-size-14 govuk-link"
|
||||
>
|
||||
{blob.name} (
|
||||
{bytesToSize(
|
||||
blob.contentLength ||
|
||||
blob.size
|
||||
)}
|
||||
)
|
||||
</Link>{" "}
|
||||
{blob.lastModified && (
|
||||
<span className="govuk-!-font-size-14">
|
||||
{/* {
|
||||
blob.displayDate
|
||||
} */}
|
||||
{formatBlobDates(
|
||||
blob
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// components/admin/utils/adminStorage.js
|
||||
|
||||
/**
|
||||
* Get the folder path up to the last slash.
|
||||
* Used for grouping blobs by "case folder".
|
||||
*/
|
||||
export function getUpToLastSlash(url) {
|
||||
let lastSlash = url.lastIndexOf("/");
|
||||
|
||||
url = url.substring(0, lastSlash);
|
||||
|
||||
url =
|
||||
url.lastIndexOf("/files") > 0
|
||||
? url.substring(0, url.lastIndexOf("/files"))
|
||||
: url;
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a stream (Azure blob download) to a string.
|
||||
*/
|
||||
export async function streamToString(readableStream) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks = [];
|
||||
readableStream.on("data", (data) => {
|
||||
chunks.push(data.toString());
|
||||
});
|
||||
readableStream.on("end", () => {
|
||||
resolve(chunks.join(""));
|
||||
});
|
||||
readableStream.on("error", reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format how long ago a date occurred.
|
||||
*/
|
||||
export function formatTimeSince(date) {
|
||||
if (!date) return "Unknown date";
|
||||
|
||||
const now = new Date();
|
||||
const diffMs = now - date;
|
||||
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (diffDays < 7) {
|
||||
return `${diffDays} day${diffDays !== 1 ? "s" : ""} ago`;
|
||||
} else {
|
||||
const diffWeeks = Math.floor(diffDays / 7);
|
||||
return `${diffWeeks} week${diffWeeks !== 1 ? "s" : ""} ago`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format Azure blob created/modified dates into a readable string.
|
||||
*/
|
||||
export function formatBlobDates(blob) {
|
||||
const created = blob.createdOn ? new Date(blob.createdOn) : null;
|
||||
const modified = blob.lastModified ? new Date(blob.lastModified) : null;
|
||||
|
||||
if (!created && !modified) return "Unknown";
|
||||
|
||||
// If both exist and are the same
|
||||
if (created && modified && created.getTime() === modified.getTime()) {
|
||||
return `${created.toLocaleString("en-GB")} (${formatTimeSince(
|
||||
created
|
||||
)})`;
|
||||
}
|
||||
|
||||
// If modified exists and is newer than created
|
||||
if (created && modified && modified.getTime() > created.getTime()) {
|
||||
return `Created: ${created.toLocaleString("en-GB")} (${formatTimeSince(
|
||||
created
|
||||
)}), Modified: ${modified.toLocaleString("en-GB")} (${formatTimeSince(
|
||||
modified
|
||||
)})`;
|
||||
}
|
||||
|
||||
// Only one date exists
|
||||
const date = modified || created;
|
||||
return `${date.toLocaleString("en-GB")} (${formatTimeSince(date)})`;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// lib/prisma.js
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const globalForPrisma = globalThis;
|
||||
|
||||
export const prisma =
|
||||
globalForPrisma.prisma ||
|
||||
new PrismaClient({
|
||||
log: ["query", "error", "warn"],
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
globalForPrisma.prisma = prisma;
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
// components/admin/utils/serverStorage.js
|
||||
import { DefaultAzureCredential } from "@azure/identity";
|
||||
import { BlobServiceClient } from "@azure/storage-blob";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import CryptoJS from "crypto-js";
|
||||
import { getUpToLastSlash, streamToString } from "../utils/adminHelper";
|
||||
import { listContainersForUser } from "../../../actions/azurestorage";
|
||||
|
||||
const prisma = global.prisma || new PrismaClient();
|
||||
|
||||
/**
|
||||
* Generate HMAC hash for blob API link.
|
||||
*/
|
||||
function hashAPIPath(queryPath, WORDKEY) {
|
||||
let hashlink = CryptoJS.HmacSHA256(
|
||||
queryPath,
|
||||
CryptoJS.enc.Hex.parse(WORDKEY)
|
||||
);
|
||||
return hashlink.toString(CryptoJS.enc.Hex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch blob structure + users for admin storage page.
|
||||
*/
|
||||
export async function fetchAdminStorageData() {
|
||||
const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME;
|
||||
const creds = new DefaultAzureCredential();
|
||||
const blobServiceClient = new BlobServiceClient(
|
||||
`https://${accountName}.blob.core.windows.net`,
|
||||
creds
|
||||
);
|
||||
|
||||
const prisma = global.prisma || new PrismaClient();
|
||||
|
||||
const users = await prisma.user.findMany();
|
||||
|
||||
let data = await Promise.all(
|
||||
users.map(async (user) => {
|
||||
const containers = await listContainersForUser(
|
||||
blobServiceClient,
|
||||
`${user.id}`,
|
||||
user.email
|
||||
);
|
||||
|
||||
const containersWithHashes = await Promise.all(
|
||||
containers.map(async (container) => {
|
||||
const groupedByFolder = await container.blobs.reduce(
|
||||
async (accP, blob) => {
|
||||
const acc = await accP;
|
||||
const folderPath =
|
||||
getUpToLastSlash(blob.name.trim()) || "";
|
||||
|
||||
const hashedfilepath = hashAPIPath(
|
||||
`/api/file/downloadblob?container=${
|
||||
container.container
|
||||
}&casefolderID=${folderPath}&blobname=${blob.name
|
||||
.substring(blob.name.lastIndexOf("/") + 1)
|
||||
.trim()}`,
|
||||
process.env.HASHKEY
|
||||
);
|
||||
|
||||
const fileNameOnly = blob.name.substring(
|
||||
blob.name.lastIndexOf("/") + 1
|
||||
);
|
||||
const isTmpFile = fileNameOnly.startsWith("TMP-");
|
||||
|
||||
const blobWithHash = {
|
||||
...blob,
|
||||
hashedfilepath,
|
||||
isTmpFile,
|
||||
};
|
||||
|
||||
let folderGroup = acc.find(
|
||||
(f) => f.folderPath === folderPath
|
||||
);
|
||||
if (!folderGroup) {
|
||||
folderGroup = {
|
||||
folderPath,
|
||||
blobs: [],
|
||||
hasRepJson: false,
|
||||
repJsonBlob: null,
|
||||
repComplete: false,
|
||||
hasTmpFile: false,
|
||||
};
|
||||
acc.push(folderGroup);
|
||||
}
|
||||
|
||||
folderGroup.blobs.push(blobWithHash);
|
||||
|
||||
if (isTmpFile) {
|
||||
folderGroup.hasTmpFile = true;
|
||||
}
|
||||
|
||||
// rep.json handling
|
||||
if (blob.name.toLowerCase().endsWith("rep.json")) {
|
||||
folderGroup.hasRepJson = true;
|
||||
folderGroup.repJsonBlob = blobWithHash;
|
||||
|
||||
const containerClient =
|
||||
blobServiceClient.getContainerClient(
|
||||
container.container
|
||||
);
|
||||
const blockBlobClient =
|
||||
containerClient.getBlockBlobClient(
|
||||
blob.name
|
||||
);
|
||||
|
||||
try {
|
||||
const downloadResponse =
|
||||
await blockBlobClient.download(0);
|
||||
const downloaded = await streamToString(
|
||||
downloadResponse.readableStreamBody
|
||||
);
|
||||
const json = JSON.parse(downloaded);
|
||||
folderGroup.repComplete = Boolean(
|
||||
json.repComplete
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(
|
||||
"Error reading rep.json",
|
||||
err
|
||||
);
|
||||
folderGroup.repComplete = false;
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
Promise.resolve([])
|
||||
);
|
||||
|
||||
return { ...container, folders: groupedByFolder };
|
||||
})
|
||||
);
|
||||
|
||||
return containersWithHashes.length > 0
|
||||
? {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
containers: containersWithHashes,
|
||||
}
|
||||
: null;
|
||||
})
|
||||
);
|
||||
|
||||
data = data.filter(Boolean);
|
||||
|
||||
let userData = users
|
||||
.filter((user) => user.email && user.email.trim() !== "")
|
||||
.map((user) => ({
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
created: user.emailVerified?.toLocaleString("en-GB") || "",
|
||||
}));
|
||||
|
||||
let repCompleteCount = 0;
|
||||
data.forEach((user) => {
|
||||
user.containers.forEach((container) => {
|
||||
container.folders.forEach((folder) => {
|
||||
if (folder.repComplete === true) repCompleteCount++;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return { data, userData, repCompleteCount };
|
||||
}
|
||||
@@ -17,9 +17,11 @@ 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 } = useTranslation();
|
||||
let { t, lang } = useTranslation();
|
||||
const firstRender = useRef(false);
|
||||
const {
|
||||
incidentid,
|
||||
@@ -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`
|
||||
@@ -419,7 +418,7 @@ const DocumentDetails = (props) => {
|
||||
] +
|
||||
'")].value_cy',
|
||||
json: transLookup,
|
||||
sanbox: {},
|
||||
eval: true,
|
||||
})
|
||||
: detailsObj[
|
||||
"pinswg_isharedocumentlocations@OData.Community.Display.V1.FormattedValue"
|
||||
@@ -507,7 +506,7 @@ const DocumentDetails = (props) => {
|
||||
</p>
|
||||
<div>
|
||||
<button onClick={clearCheckboxes}>
|
||||
{t("case:documents-clear-filter-label")}
|
||||
{t("search:clear-filter-button-label")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="govuk-checkboxes govuk-checkboxes--small">
|
||||
@@ -584,8 +583,8 @@ const DocumentDetails = (props) => {
|
||||
'$..[?(@ && @.value=="' +
|
||||
item.pinswg_isharedocumentlocationsLabel +
|
||||
'")].value_cy',
|
||||
jsonL: transLookup,
|
||||
sanbox: {},
|
||||
json: transLookup,
|
||||
eval: true,
|
||||
})
|
||||
: item.pinswg_isharedocumentlocationsLabel ||
|
||||
t(
|
||||
@@ -611,7 +610,7 @@ const DocumentDetails = (props) => {
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
>
|
||||
{t("case:documents-filter-button")}
|
||||
{t("search:filter-documents-label")}
|
||||
</button>
|
||||
</div>
|
||||
{documentDetailsObj["@odata.count"] > 5 && (
|
||||
@@ -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,
|
||||
@@ -1068,6 +1059,7 @@ const DocumentDetails = (props) => {
|
||||
orderByState,
|
||||
getDocumentTypes,
|
||||
getDocumentResults,
|
||||
lang,
|
||||
]);
|
||||
return (
|
||||
<div className="govuk-grid-row">
|
||||
|
||||
@@ -57,6 +57,7 @@ const CaseSummary = (props) => {
|
||||
const router = useRouter();
|
||||
const { locale } = router;
|
||||
const {
|
||||
appealTypeID,
|
||||
incidentid,
|
||||
caseReference,
|
||||
myCases,
|
||||
@@ -76,9 +77,9 @@ const CaseSummary = (props) => {
|
||||
currentView,
|
||||
messagesObj,
|
||||
docsOffline,
|
||||
ticketnumber,
|
||||
} = props;
|
||||
|
||||
let appealTypeID = props.appealTypeID || router.query.appealType;
|
||||
const showLoginCheck =
|
||||
currentType == "directResultsObj" ? "true" : currentView.showLogin;
|
||||
const { data: session, status } = useSession();
|
||||
@@ -233,13 +234,13 @@ const CaseSummary = (props) => {
|
||||
|
||||
currentType == "searchResultsObj"
|
||||
? (casesObj = jsonpath({
|
||||
path: '$..[?(@ && @.title=="' + caseReference + '")]',
|
||||
path: '$..[?(@ && @.ticketnumber=="' + ticketnumber + '")]',
|
||||
json: searchResultsObj,
|
||||
eval: true,
|
||||
}))
|
||||
: currentType == "watchedCases"
|
||||
? (casesObj = jsonpath({
|
||||
path: '$..[?(@ && @.pinswg_title=="' + caseReference + '")]',
|
||||
path: '$..[?(@ && @.ticketnumber=="' + ticketnumber + '")]',
|
||||
json: setCaseQueryObj,
|
||||
eval: true,
|
||||
}))
|
||||
@@ -251,7 +252,7 @@ const CaseSummary = (props) => {
|
||||
}))
|
||||
: currentType == "directResultsObj"
|
||||
? (casesObj = jsonpath({
|
||||
path: '$..[?(@ && @.ticketnumber=="' + caseReference + '")]',
|
||||
path: '$..[?(@ && @.ticketnumber=="' + ticketnumber + '")]',
|
||||
json: setCaseQueryObj,
|
||||
eval: true,
|
||||
}))
|
||||
@@ -481,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(
|
||||
@@ -541,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(
|
||||
@@ -1955,7 +1963,9 @@ const CaseSummary = (props) => {
|
||||
|
||||
useEffect(() => {
|
||||
const linkCaseObj = async () => {
|
||||
let docObj = await getLinkedCases(incidentid).then((data) => {
|
||||
let docObj = await getLinkedCases(
|
||||
incidentid || setCaseQueryObj.value[0].incidentid
|
||||
).then((data) => {
|
||||
//console.log(data);
|
||||
setCurrentLinkedCases(data);
|
||||
return data;
|
||||
@@ -1972,6 +1982,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:
|
||||
@@ -2019,6 +2090,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,
|
||||
|
||||
@@ -4,8 +4,14 @@ import CRMError from "./crmError";
|
||||
import DNSSearchResults from "./search/dnssearchresults";
|
||||
|
||||
export default function Search(props) {
|
||||
const { searchString, searchResultsObj, myportal, watchedCases, showMap } =
|
||||
props;
|
||||
const {
|
||||
searchString,
|
||||
searchResultsObj,
|
||||
myportal,
|
||||
watchedCases,
|
||||
showMap,
|
||||
showLoginCheck,
|
||||
} = props;
|
||||
let { t } = useTranslation();
|
||||
|
||||
const router = useRouter();
|
||||
@@ -41,6 +47,7 @@ export default function Search(props) {
|
||||
myportal={myportal}
|
||||
watchedCases={watchedCases}
|
||||
showMap={showMap}
|
||||
showLoginCheck={showLoginCheck}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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"
|
||||
@@ -168,6 +170,7 @@ const MyPortal = (props) => {
|
||||
watchedCases={props.watchedCases}
|
||||
isLPA={isLPA}
|
||||
myReps={false}
|
||||
accountDetails={props.accountDetails}
|
||||
/>
|
||||
)}
|
||||
{props.myRepresentations.hasOwnProperty(
|
||||
|
||||
@@ -34,12 +34,11 @@ const AwaitingSubmission = (props) => {
|
||||
<div className="cardVieAll">
|
||||
<Link
|
||||
href={
|
||||
(router.locale != "en"
|
||||
router.locale != "en"
|
||||
? "/" +
|
||||
router.locale +
|
||||
"/fymhorth/gweldpopeth"
|
||||
: "/myportal/viewall") +
|
||||
"?viewKey=awaitingSubmission"
|
||||
"/fymhorth/gweldpopeth?key=awaitingSubmission"
|
||||
: "/myportal/viewall?key=awaitingSubmission"
|
||||
}
|
||||
className="govuk-link--no-underline"
|
||||
onClick={() => {
|
||||
|
||||
@@ -351,12 +351,11 @@ const AwaitingSubmissionFromBlob = (props) => {
|
||||
<div className="cardVieAll">
|
||||
<Link
|
||||
href={
|
||||
(router.locale != "en"
|
||||
router.locale != "en"
|
||||
? "/" +
|
||||
router.locale +
|
||||
"/fymhorth/gweldpopeth"
|
||||
: "/myportal/viewall") +
|
||||
"?viewKey=awaitingSubmissionDetails"
|
||||
"/fymhorth/gweldpopeth?key=awaitingSubmissionDetails"
|
||||
: "/myportal/viewall?key=awaitingSubmissionDetails"
|
||||
}
|
||||
className="govuk-link--no-underline"
|
||||
onClick={() => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -29,11 +29,11 @@ const MyCases = (props) => {
|
||||
<div className="cardVieAll">
|
||||
<Link
|
||||
href={
|
||||
(router.locale != "en"
|
||||
router.locale != "en"
|
||||
? "/" +
|
||||
router.locale +
|
||||
"/fymhorth/gweldpopeth"
|
||||
: "/myportal/viewall") + "?viewKey=myCases"
|
||||
"/fymhorth/gweldpopeth?key=myCases"
|
||||
: "/myportal/viewall?key=myCases"
|
||||
}
|
||||
className="govuk-link--no-underline"
|
||||
onClick={() => {
|
||||
|
||||
@@ -34,12 +34,11 @@ const MyRepresentations = (props) => {
|
||||
<div className="cardVieAll">
|
||||
<Link
|
||||
href={
|
||||
(router.locale != "en"
|
||||
router.locale != "en"
|
||||
? "/" +
|
||||
router.locale +
|
||||
"/fymhorth/gweldpopeth"
|
||||
: "/myportal/viewall") +
|
||||
"?viewKey=myRepresentations"
|
||||
"/fymhorth/gweldpopeth?key=myRepresentations"
|
||||
: "/myportal/viewall?key=myRepresentations"
|
||||
}
|
||||
className="govuk-link--no-underline"
|
||||
onClick={() => {
|
||||
|
||||
@@ -33,12 +33,11 @@ const MyRepresentations = (props) => {
|
||||
<div className="cardVieAll">
|
||||
<Link
|
||||
href={
|
||||
(router.locale != "en"
|
||||
router.locale != "en"
|
||||
? "/" +
|
||||
router.locale +
|
||||
"/fymhorth/gweldpopeth"
|
||||
: "/myportal/viewall") +
|
||||
"?viewKey=myRepresentations"
|
||||
"/fymhorth/gweldpopeth?key=myRepresentations"
|
||||
: "/myportal/viewall?key=myRepresentations"
|
||||
}
|
||||
className="govuk-link--no-underline"
|
||||
onClick={() => {
|
||||
|
||||
@@ -40,12 +40,11 @@ const MySubmittedReps = (props) => {
|
||||
<div className="cardVieAll">
|
||||
<Link
|
||||
href={
|
||||
(router.locale != "en"
|
||||
router.locale != "en"
|
||||
? "/" +
|
||||
router.locale +
|
||||
"/fymhorth/gweldpopeth"
|
||||
: "/myportal/viewall") +
|
||||
"?viewKey=mySubmittedReps"
|
||||
"/fymhorth/gweldpopeth?key=mySubmittedReps"
|
||||
: "/myportal/viewall?key=mySubmittedReps"
|
||||
}
|
||||
className="govuk-link--no-underline"
|
||||
onClick={() => {
|
||||
|
||||
@@ -43,6 +43,11 @@ const ServiceBanner = (props) => {
|
||||
{portalLogoLink.includes(router.pathname) ? (
|
||||
<Link href={"/myportal"}>
|
||||
<img
|
||||
className={
|
||||
router.locale == "cy"
|
||||
? "serviceTitle cy"
|
||||
: "serviceTitle"
|
||||
}
|
||||
src={
|
||||
router.locale == "cy"
|
||||
? "/assets/images/planning-casework-cy.svg"
|
||||
@@ -57,6 +62,11 @@ const ServiceBanner = (props) => {
|
||||
</Link>
|
||||
) : (
|
||||
<img
|
||||
className={
|
||||
router.locale == "cy"
|
||||
? "serviceTitle cy"
|
||||
: "serviceTitle"
|
||||
}
|
||||
src={
|
||||
router.locale == "cy"
|
||||
? "/assets/images/planning-casework-cy.svg"
|
||||
@@ -75,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}
|
||||
|
||||
@@ -31,6 +31,7 @@ const WatchedCases = (props) => {
|
||||
currentView={props.currentView}
|
||||
myReps={props.myReps}
|
||||
props={props}
|
||||
accountDetails={props.accountDetails}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -40,12 +41,11 @@ const WatchedCases = (props) => {
|
||||
<div className="cardVieAll">
|
||||
<Link
|
||||
href={
|
||||
(router.locale != "en"
|
||||
router.locale != "en"
|
||||
? "/" +
|
||||
router.locale +
|
||||
"/fymhorth/gweldpopeth"
|
||||
: "/myportal/viewall") +
|
||||
"?viewKey=watchedCases"
|
||||
"/fymhorth/gweldpopeth?key=watchedCases"
|
||||
: "/myportal/viewall?key=watchedCases"
|
||||
}
|
||||
className="govuk-link--no-underline"
|
||||
onClick={() => {
|
||||
|
||||
@@ -311,8 +311,8 @@ const SearchResults = (props) => {
|
||||
resultsArrPaged.map((item, key) => {
|
||||
let detailsObj = jsonpath({
|
||||
path:
|
||||
'$..value[?(@ && @.pinswg_name=="' +
|
||||
item.pinswg_name +
|
||||
'$..value[?(@ && @.ticketnumber=="' +
|
||||
item.ticketnumber +
|
||||
'")]',
|
||||
|
||||
json: searchDetailsObj,
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -18,7 +18,11 @@ export default function ServiceBanner(props) {
|
||||
<div className="servicebanner">
|
||||
<h1>
|
||||
<img
|
||||
className="govuk-!-margin-top-4"
|
||||
className={
|
||||
router.locale == "cy"
|
||||
? "govuk-!-margin-top-4 serviceTitle cy"
|
||||
: "govuk-!-margin-top-4 serviceTitle"
|
||||
}
|
||||
src={
|
||||
router.locale == "cy"
|
||||
? "/assets/images/planning-casework-cy.svg"
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
+59
-21
@@ -34,6 +34,15 @@ export const getFormCollectionByID = (appealTypeID) => {
|
||||
return collectionName[0];
|
||||
};
|
||||
|
||||
export const getNavigationPropertyByPrimaryAttribute = (primaryAttribute) => {
|
||||
const collectionName = jsonpath({
|
||||
path: "$..[?(@ && @.PrimaryIdAttribute =='" + primaryAttribute + "')]",
|
||||
json: data,
|
||||
eval: true,
|
||||
});
|
||||
return collectionName[0];
|
||||
};
|
||||
|
||||
export const getFormIDByLogicalName = (appealTypeLogicalName) => {
|
||||
const collectionName = jsonpath({
|
||||
path: "$..[?(@ && @.LogicalName =='" + appealTypeLogicalName + "')]",
|
||||
@@ -170,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) => {
|
||||
|
||||
Reference in New Issue
Block a user