import { JSONPath as jsonpath } from "jsonpath-plus"; import useTranslation from "next-translate/useTranslation"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import { connect } from "react-redux"; import { reduxForm } from "redux-form"; import { useSession } from "next-auth/react"; import { generateRepPDF, uploadRepFiles } from "../../../actions/services/documentService"; import { sendEmail } from "../../../actions/services/notifyService"; import { formatDates, updateLinks } from "../../../components/utils"; import { setRepresentationCapacity, setRepresentationMessageSent, setRepresentationReset, setRepresentationSubmit, setRepresentationSubmitConfirmation } from "../../../store/currentView/action"; import ConsultationAgent from "./consultationAgent"; import ConsultationAppellant from "./consultationAppellant"; import ConsultationInterestedPartyPerson from "./consultationInterestedPartyPerson"; import RepAgent from "./representationAgent"; import RepAppellant from "./representationAppellant"; import RepCapacitySelection from "./representationCapacitySelection"; import RepCompleteSubmit from "./representationCompleteSubmit"; import RepInterestedPartyPerson from "./representationInterestedPartyPerson"; import RepLPA from "./representationLPA"; import RepLPACapacitySelection from "./representationLPACapacitySelection"; import RepLandOwner from "./representationLandowner"; import RepresentationProgress_enforcement from "./representationProgress/representationProgress_enforcement"; import RepresentationProgress_s78 from "./representationProgress/representationProgress_s78"; import RepresentationFlowFormStage from "./shell/RepresentationFlowFormStage"; import { getQuestionnaireNextSection, resolveJourneyStageFlags, resolveRepresentationControlKey, resolveSubmitTransition } from "./utils/stepResolution"; import { findDetailsObjForSubmit, findDetailsObjForView, findResultsObjForCurrentReference, resolveInitialCaseDataSources, resolveSubmitCaseDetailsSource } from "./utils/dataResolution"; import { buildRepresentationUpdateBody, buildSubmitEnrichedValues, runFinalisationSequence } from "./utils/finalisationBoundary"; import { buildRepresentationContext } from "./utils/buildRepsArrRules"; import { getAvailableRepresentationTypes } from "../../../lib/domain/representation-type-policy/getAvailableRepresentationTypes"; let MakeRepresentation = (props) => { let { t } = useTranslation(); const [savingStatus, setSavingStatus] = useState(false); const [finaliseAppealProcess, setFinaliseAppealProcess] = useState(false); const [representationTypeConfirmed, setRepresentationTypeConfirmed] = useState( !!props?.currentView?.caseReference?.repDetails?.representationType ); //const [questionnaireCount, setQuestionnaireCount] = useState(0); const { data: session } = useSession(); const router = useRouter(); const { locale } = router; const { caseReference, myCases, myRepresentations, watchedCases, awaitingSubmission, searchResultsObj, representationSubmit, currentType, currentView, setRepresentationCapacity, setRepresentationSubmit, setSubmitBack, handleSubmit, setRepresentationSubmitConfirmation, setRepresentationMessageSent, setShowQuestionnaireSection, showQuestionnaireSection, initialValues, setCurrentReference, ticketnumber, docsOffline } = props; const formObj = props.props.form; let setCaseQueryObj = props[currentType] || {}; var casesObj = {}; const appealType = currentView.caseReference.appealType; const accountDetails = props.props.accountDetails.accountDetails; const caseReferenceObj = props.props.currentView.caseReference; const updateQuestionnaireCount = () => { const countMap = { 846040000: 7, 846040001: 7, 846040025: 7, 846040004: 7, 846040018: 7, 846040003: 7, 846040009: 7, 846040008: 7, 846040005: 9, 846040006: 9, 846040007: 9 }; return countMap[appealType] || 7; }; let questionnaireCount = updateQuestionnaireCount(); const isLPA = accountDetails?.[ "pinswg_typeofinvolvement@OData.Community.Display.V1.FormattedValue" ] == "LPA" ? true : false; const isNRW = currentView.caseReference?.isNRW; const { showCheckStage } = resolveJourneyStageFlags({ "representationSubmit": currentView.representationSubmit, "representationSubmitConfirmation": currentView.representationSubmitConfirmation }); const setRepFileName = (values) => { if ( !values.hasOwnProperty("representationCapacity") || !values.hasOwnProperty("representationType") ) { return values; } const repCapMap = { [t("myrepresentations:capacity-options-arr-appellant")]: "APP", [t("myrepresentations:capacity-options-arr-agent")]: "APP", // agent treated as appellant proxy [t("myrepresentations:capacity-options-arr-interested")]: "IP", "Land Owner": "LO", lpa: "LPA", LPA: "LPA", NRW: "NRW" }; const repTypeMap = { Statement: "Statement", "Statement of common ground": "SCG", "Written statement": "WS", "Written statement of evidence": "WSE", Questionnaire: "Questionnaire", "Final comments": "Comments", "Consultation Response": "Consultation_Response", "Local Impact Report": "Local_Impact_Report", "Marine Impact Report": "Marine_Impact_Report", Other: "OTHER" }; const now = new Date(); const pad = (num) => String(num).padStart(2, "0"); const dateStr = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad( now.getDate() )}-${pad(now.getHours())}:${pad(now.getMinutes())}:${pad( now.getSeconds() )}`; const repCap = repCapMap[values.representationCapacity] || ""; const repType = repTypeMap[values.representationType] || ""; const sanitize = (str) => str.replace(/[\s~`!@#$%^&*(){}\[\];:"'<,.>?\/\\|_+=-]/g, ""); const namePart = isLPA ? sanitize(values.onBehalfOfLPA || "") : `${sanitize(values.lastname || "")}_${sanitize( values.firstname || "" )}`; values.repfile_name = values.repfile_name || `${dateStr}_-_${repCap}_-_${repType}_-_${namePart}`; return values; }; useEffect(() => { if (isLPA && !currentView?.representationCapacity) { setRepresentationCapacity("LPA"); } }, [isLPA, currentView?.representationCapacity, setRepresentationCapacity]); const normalizeCapacity = (rawCapacity) => { const normalized = (rawCapacity || "") .toString() .toLowerCase() .replace(/(\band|[\s\-\+\(\)\,\&])/g, ""); const capacityMap = { appellant: "appellant", apelydd: "appellant", agent: "agent", asiant: "agent", interestedparty: "interestedparty", lpa: "lpa", landowner: "landowner", [t("myrepresentations:capacity-options-arr-appellant") .toLowerCase() .replace(/(\band|[\s\-\+\(\)\,\&])/g, "")]: "appellant", [t("myrepresentations:capacity-options-arr-agent") .toLowerCase() .replace(/(\band|[\s\-\+\(\)\,\&])/g, "")]: "agent", [t("myrepresentations:capacity-options-arr-interested") .toLowerCase() .replace(/(\band|[\s\-\+\(\)\,\&])/g, "")]: "interestedparty" }; return capacityMap[normalized] || normalized || "false"; }; const getCasePath = () => { switch (currentType) { case "searchResultsObj": return { path: `$..[?(@ && @.title=="${caseReference}")]`, json: setCaseQueryObj }; case "watchedCases": return { path: `$..[?(@ && @.ticketnumber=="${caseReference}")]`, json: watchedCases }; default: return { path: `$..[?(@ && @.ticketnumber=="${ticketnumber}")]`, json: setCaseQueryObj }; } }; const { path, json } = getCasePath(); casesObj = jsonpath({ path, json, eval: true }); casesObj = Object.assign({}, ...casesObj); var detailsObj = {}; // const isAppellant = // props.props.accountDetails.accountDetails.pinswg_typeofinvolvement === // 846040001; const isAppellant = props.props.accountDetails.accountDetails.emailaddress1 == props.searchResultsObj?.searchResultsObj?.value[0].customerid_contact .emailaddress1; const capacityOptionsArr = []; isAppellant && capacityOptionsArr.push( t("myrepresentations:capacity-options-arr-appellant") ); const shouldIncludeInterested = appealType !== 846040017 || caseReferenceObj.specialistProcess !== 846040000; if (shouldIncludeInterested) { capacityOptionsArr.push( t("myrepresentations:capacity-options-arr-interested"), t("myrepresentations:capacity-options-arr-agent") ); } const isEditState = router.query.state === "edit"; const hasState = router.query.hasOwnProperty("state"); const { caseDetailsSource, caseResultsSource } = resolveInitialCaseDataSources({ hasState, isEditState, currentType: props.currentType, myRepresentations: props.props.myRepresentations.myRepresentations, searchDetailsObj: props.props.searchResultsObj.searchDetailsObj, searchResultsObj: props.props.searchResultsObj.searchResultsObj, watchedCasesDetails: props.props.watchedCases.watchedCasesDetails }); let resultsObj = findResultsObjForCurrentReference({ "currentReference": currentView.caseReference.currentReference, "caseResultsSource": caseResultsSource }); // let detailsObj; detailsObj = findDetailsObjForView({ hasState, isEditState, "queryCreated": router.query.created, "ticketnumber": currentView.caseReference.ticketnumber, "caseDetailsSource": caseDetailsSource }); const safeDetailsObj = detailsObj || {}; const safeResultsObj = resultsObj || {}; const buildRepsArr = (detailsObj) => { const isCaseOwner = props.props.accountDetails.accountDetails.emailaddress1 == props.searchResultsObj?.searchResultsObj?.value[0] .customerid_contact.emailaddress1; const ctx = buildRepresentationContext({ detailsObj, appealType, specialistProcess: currentView.caseReference.specialistProcess, involvementType: props.props?.accountDetails?.accountDetails ?.pinswg_typeofinvolvement, selectedCapacity: repCapacityType(), isCaseOwner, isNRW }); return getAvailableRepresentationTypes(ctx); }; const repCapacityType = () => { const repForm = formObj["representationForm"]; if ( !repForm || !repForm.values || !repForm.values.representationCapacity ) { return false; } return repForm.values.representationCapacity .replace(/(\band|[\s\-\+\(\)\,\&])/g, "") .toLowerCase(); }; const whichControl = () => { const commonProps = { formObj, setRepresentationCapacity, setRepresentationSubmit, onHandleSubmit, currentView, setSubmitBack, props, setRepFileName, updateRepresentation, setSavingStatus, savingStatus, setCurrentReference, onRepresentationTypeChange: () => setRepresentationTypeConfirmed(false), onTypeSelectionConfirmed: () => setRepresentationTypeConfirmed(true) }; const { controlKey } = resolveRepresentationControlKey({ "isLPA": isLPA, "representationCapacity": currentView.representationCapacity, "repDetailsCapacity": currentView.caseReference?.repDetails?.representationCapacity, "appealType": appealType, "normalizeCapacity": normalizeCapacity }); if (controlKey === "lpa-capacity-selection") { return ( ); } switch (controlKey) { case "capacity-appellant": { const Component = appealType === 846040002 ? ConsultationAppellant : RepAppellant; return ( ); } case "capacity-lpa": { return ( ); } case "capacity-agent": { const Component = appealType === 846040002 ? ConsultationAgent : RepAgent; return ( ); } case "capacity-interestedparty": { const Component = appealType === 846040002 ? ConsultationInterestedPartyPerson : RepInterestedPartyPerson; return ( ); } case "capacity-landowner": { return ( ); } default: return ( ); } }; const whichProgress = () => { const commonProps = { props, containerID: props.props.accountDetails.containerID, currentView, showQuestionnaireSection, setShowQuestionnaireSection, questionnaireCount }; const s78AppealTypes = [ 846040000, 846040001, 846040025, 846040004, 846040018, 846040003, 846040009, 846040008, 846040017 ]; const enforcementAppealTypes = [846040005, 846040006, 846040007]; if (s78AppealTypes.includes(appealType)) { return ; } if (enforcementAppealTypes.includes(appealType)) { return ; } return <>; }; const sortFileObj = (filelistObj) => { const cleanArray = (arr) => arr .filter((obj) => obj !== null) .filter((obj) => Object.keys(obj).length > 0) .filter((obj) => obj.name !== undefined); const deduplicate = (arr) => { const seen = new Set(); return arr.filter((obj) => { const serialized = JSON.stringify(obj); if (seen.has(serialized)) return false; seen.add(serialized); return true; }); }; const extractFilesFromField = (filesObj) => { return Object.values(filesObj).reduce((acc, file) => { if (file?.name) { acc.push({ name: file.name, size: file.size }); } return acc; }, []); }; const cleanedBlobList = cleanArray(filelistObj); const extractedFiles = extractFilesFromField(field.input.value); const mergedFiles = [...extractedFiles, ...cleanedBlobList]; return deduplicate(mergedFiles); }; const buildAddressStr = (detailsObj) => { const toTitleCase = (str) => str.toLowerCase().replace(/\b\w/g, (char) => char.toUpperCase()); return [ detailsObj?.pinswg_siteaddressline1 || detailsObj?.pinswg_addressline1, detailsObj?.pinswg_siteaddressline2 || detailsObj?.pinswg_addressline2, detailsObj?.pinswg_siteaddresstown || detailsObj?.pinswg_addresstown, detailsObj?.pinswg_siteaddresspostcode || detailsObj?.pinswg_postcode, detailsObj?.pinswg_siteaddresscounty || detailsObj?.pinswg_addresscounty ] .filter((v) => !!v && v.trim()) // remove empty/whitespace-only values .map((v) => toTitleCase(v.trim())) // title-case each segment .join(" "); }; const updateRepresentation = async ( values, sendSavedEmail, useSaveStatus ) => { const { appealType: appealTypeProp, currentView, accountDetails, loggedinUserEmail } = props.props; const incidentID = props.appealType.caseReference.incidentid; const currentRef = caseReferenceObj.ticketnumber; const containerID = accountDetails.containerID; setSavingStatus(useSaveStatus); const getSiteAddress = () => { const { pinswg_siteaddressline1, pinswg_siteaddressline2, pinswg_siteaddresstown, pinswg_siteaddresspostcode } = safeDetailsObj; return [ pinswg_siteaddressline1, pinswg_siteaddressline2, pinswg_siteaddresstown, pinswg_siteaddresspostcode ] .filter(Boolean) .join(" "); }; const getRepresentationCapacity = () => { if ( router.query.hasOwnProperty("state") && router.query.state === "edit" ) { return currentView.representationCapacity; } return isLPA ? "LPA" : values.representationCapacity; }; const baseBody = { containerID, appealType: appealType || caseReferenceObj.repDetails.appealType, incidentID: caseReferenceObj.incidentid || caseReferenceObj.repDetails.incidentid, caseRef: currentRef, casereference: currentRef, pinswg_questionnaireduedate: safeDetailsObj.pinswg_questionnaireduedate, pinswg_statementduedate: safeDetailsObj.pinswg_statementduedate || safeDetailsObj.pinswg_endofrepresentationperiod, pinswg_statementsduedate: safeDetailsObj.pinswg_statementsduedate || safeDetailsObj.pinswg_endofrepresentationperiod, pinswg_finalcommentsduedate: safeDetailsObj.pinswg_finalcommentsduedate || safeDetailsObj.pinswg_endofrepresentationperiod, pinswg_name: currentRef, firstname: accountDetails.firstname, lastname: accountDetails.lastname, emailAddress: accountDetails.emailaddress1, siteAddress: getSiteAddress(), pinswg_siteaddressline1: safeDetailsObj.pinswg_siteaddressline1, pinswg_siteaddressline2: safeDetailsObj.pinswg_siteaddressline2, pinswg_siteaddresstown: safeDetailsObj.pinswg_siteaddresstown, pinswg_siteaddresspostcode: safeDetailsObj.pinswg_siteaddresspostcode, pinswg_lpareference: safeResultsObj?.pinswg_lpareference, representationCapacity: getRepresentationCapacity(), locale: router.locale }; const formattedValues = { ...values, ...baseBody, _pinswg_appellant_value: safeDetailsObj._pinswg_appellant_value, "_pinswg_appellant_value@OData.Community.Display.V1.FormattedValue": safeDetailsObj[ "_pinswg_appellant_value@OData.Community.Display.V1.FormattedValue" ], _pinswg_localplanningauthority_value: safeDetailsObj._pinswg_localplanningauthority_value || safeDetailsObj._pinswg_associatedlpa_value, "_pinswg_localplanningauthority_value@OData.Community.Display.V1.FormattedValue": safeDetailsObj[ "_pinswg_localplanningauthority_value@OData.Community.Display.V1.FormattedValue" ] || safeDetailsObj[ "_pinswg_associatedlpa_value@OData.Community.Display.V1.FormattedValue" ] }; const updateBody = buildRepresentationUpdateBody({ formattedValues, setRepFileName }); const sendEmailIfNeeded = () => { if (sendSavedEmail) { const reference = "PEDW-PARTIAL-REP"; const templateId = "021b0a7b-df00-41f1-b94e-c269bee98c75"; const personalisation = { caseReference: props.caseReference, emailAddress: loggedinUserEmail, returnLink: "", linkExpiry: 600 // 10 minutes }; sendEmail( templateId, loggedinUserEmail, personalisation, reference ); } }; const redirectToPortal = () => { const base = router.locale !== "en" ? `/${router.locale}/fymhorth` : "/myportal"; router.replace(base); }; const handleFileUpload = () => { const filesWithLinks = updateLinks({ ...values, filesList: currentView.fileList }); uploadRepresentationFiles(filesWithLinks); }; const isSubmitted = currentView.representationSubmit === true || currentView.representationSubmit === "true"; if (isSubmitted) { sendEmailIfNeeded(); if (useSaveStatus) redirectToPortal(); } else { handleFileUpload(); if (useSaveStatus) { redirectToPortal(); } } console.log("Rep Updated - ", props.caseReference); }; const onHandleSubmit = (values) => { values = setRepFileName(values); if (!caseReferenceObj?.currentReference) { return; } const caseDetailsSource = resolveSubmitCaseDetailsSource({ "isEditState": router.query?.state === "edit", "currentType": props.currentType, "myRepresentations": props.props.myRepresentations.myRepresentations, "watchedCasesDetails": props.props.watchedCases.watchedCasesDetails, "searchDetailsObj": props.props.searchResultsObj.searchDetailsObj }); // Find correct case details let detailsObj = findDetailsObjForSubmit({ ticketnumber, caseDetailsSource }); // Cancel submit status if editing if (router.query?.state === "edit") { setRepresentationSubmit(false); } // If it's a new form and has values, enrich payload if ( formObj?.representationForm?.values && !router.query.hasOwnProperty("state") ) { const formValues = formObj.representationForm.values; const representationCapacity = router.query?.state === "edit" ? props.props.currentView.representationCapacity : isLPA ? "LPA" : values.representationCapacity; Object.assign( values, buildSubmitEnrichedValues({ formValues, accountDetails, appealType, caseReferenceObj, detailsObj, resultsObj, representationCapacity, locale: router.locale }) ); } const shouldSubmit = !currentView.representationSubmit; if (shouldSubmit) { const hasNoCapacity = !currentView.representationCapacity || currentView.representationCapacity === ""; const repType = values.representationType; if (hasNoCapacity) { const capacity = isLPA ? "LPA" : normalizeCapacity(values.representationCapacity); setRepresentationCapacity(capacity); } else { const submitTransition = resolveSubmitTransition({ "representationType": repType, "showQuestionnaireSection": showQuestionnaireSection, "questionnaireCount": questionnaireCount }); if (submitTransition === "advanceQuestionnaire") { setShowQuestionnaireSection( getQuestionnaireNextSection( showQuestionnaireSection, questionnaireCount ) ); updateRepresentation(values, false, false); } else { updateRepresentation(values, false, false); setRepresentationSubmit(true); } } window.scrollTo({ top: 0, behavior: "smooth" }); } else { // Already submitted console.log("capacity", currentView.representationCapacity); console.log("has errors:", props.invalid); Object.assign(values, { locale: router.locale, repComplete: currentView.caseReference.currentReference }); if (props.invalid === false) { updateRepresentation(values, false, false); } // Final PDF & upload runFinalisationSequence({ values, containerID: props.props.accountDetails.containerID, currentReference: props.currentView.caseReference.currentReference, setFinaliseAppealProcess, generateRepPDF, updateLinks, uploadRepresentationFiles, setRepresentationSubmit, setRepresentationSubmitConfirmation }).then((data) => { console.log(data, ""); }); } }; const cleanFilesList = (blobList) => { const cleanedFiles = (blobList.filesList || []) .filter((file) => file !== null) .filter( (file) => file && typeof file === "object" && Object.keys(file).length > 0 ) .filter((file) => file.name !== undefined); return { ...blobList, filesList: cleanedFiles }; }; const uploadRepresentationFiles = (values) => { // Clean files list if present const cleanedValues = values.hasOwnProperty("filesList") ? cleanFilesList(values) : values; // console.log( // "\n////////////////////////\n upload files:", // cleanedValues.containerID, // "\n////////////////////////\n fileListObj:", // fileListObj, // "\n////////////////////////\n" // ); // Prepare data object for upload const dataObj = cleanedValues; const caseFolderReference = props.caseReference || dataObj.ticketnumber || currentView?.caseReference?.ticketnumber || currentView?.caseReference?.currentReference || dataObj.caseRef || ""; // Update links on values const updatedValues = updateLinks(cleanedValues); const resolvedContainerID = updatedValues.containerID || dataObj.containerID || props?.props?.accountDetails?.containerID || props?.props?.accountDetails?.accountDetails?.containerID; // Perform file upload and return the promise return uploadRepFiles( dataObj, [{}], resolvedContainerID, `${caseFolderReference}/${dataObj.repfile_name}` ); }; const repForm = props.props.form; const isQuestionnaire = formObj?.representationForm?.values?.representationType === "Questionnaire" && representationTypeConfirmed; const appealIsConsultation = appealType === 846040002; const appealIsLocalImpact = currentView.caseReference.appealType === 846040011 || currentView.caseReference.appealType === 846040002; const renderDueDate = () => { if ( detailsObj?.pinswg_questionnaireduedate || detailsObj?.pinswg_endofrepresentationperiod || detailsObj?.pinswg_consultationclose ) { return formatDates( detailsObj.pinswg_questionnaireduedate || detailsObj.pinswg_endofrepresentationperiod || detailsObj.pinswg_consultationclose ); } return "N/A"; }; const renderStatementsDue = () => (
{t("myrepresentations:statements-due-label")}:{" "} {formatDates( safeDetailsObj.pinswg_statementduedate || safeDetailsObj.pinswg_statementsduedate || safeDetailsObj.pinswg_endofrepresentationperiod )}
); const renderMarineImpact = () => appealIsConsultation && isNRW && (
{t("myrepresentations:marine-impact-report")}:{" "} {formatDates(safeDetailsObj.pinswg_consultationclose)}
); const renderConsultationClose = () => appealIsConsultation && (
{t("myrepresentations:consultation-close-label")}:{" "} {formatDates(safeDetailsObj.pinswg_consultationclose)}
); const renderFinalCommentsDue = () => !( currentView.caseReference.appealType === 846040011 || appealIsConsultation ) && (
{t("myrepresentations:final-comments-due-label")}:{" "} {formatDates(safeDetailsObj.pinswg_finalcommentsduedate)}
); return (
{showCheckStage ? ( ) : ( )}
); }; const mapStateToProps = (state) => { return { search: state.search, searchResultsObj: state.searchResultsObj, formData: state.formData, appealType: state.appealType, currentView: state.currentView, myRepresentations: state.myRepresentations, initialValues: state.currentView.caseReference.repDetails //form: state.form, }; }; const mapDispatchToProps = (dispatch) => { return { setRepresentationCapacity: (representationCapacity) => { dispatch(setRepresentationCapacity(representationCapacity)); }, setRepresentationSubmit: (representationSubmit) => { dispatch(setRepresentationSubmit(representationSubmit)); }, setSubmitBack: () => { dispatch(setRepresentationSubmit(false)); }, setRepresentationSubmitConfirmation: () => { dispatch(setRepresentationSubmitConfirmation(true)); }, setRepresentationMessageSent: (messageSent) => { dispatch(setRepresentationMessageSent(messageSent)); }, setRepresentationReset: () => { dispatch(setRepresentationReset()); } }; }; export default connect( mapStateToProps, mapDispatchToProps )( reduxForm({ form: "representationForm", destroyOnUnmount: false, enableReinitialize: true })(MakeRepresentation) );