import useTranslation from "next-translate/useTranslation"; import dynamic from "next/dynamic"; import Link from "next/link"; import router from "next/router"; import React, { useMemo, useState, useRef } from "react"; import DatePicker from "react-datepicker"; import "react-datepicker/dist/react-datepicker.css"; import Dropzone from "react-dropzone"; import "react-quill-new/dist/quill.snow.css"; import { Field, change } from "redux-form"; import { uploadSingleFile, deleteBlob, deleteRepBlob, getFilesFromBlobHashed, getFilesFromBlobproxy } from "../../../actions/services/documentService"; const LoadingMessage = () =>
Loading the editor...
; // Dynamically import ReactQuill with a proper loading state const ReactQuill = dynamic( () => import("react-quill-new").then((mod) => mod.default), { ssr: false, loading: () => // This is valid as long as it's a valid React component } ); import { useDispatch } from "react-redux"; // Import the useDispatch hook import { addDays, addMonths, addYears, parseISO, subDays, subMonths, subYears } from "date-fns"; import { bytesToSize, getThumbnailIconByExtension, updateLinks } from "../../utils"; import { useRouter } from "next/router"; export const RenderRadioList = ({ datafieldname, name, label, id, errorMsg, options, input: { onChange, value }, meta: { touched, error }, ...custom }) => { const required = (value) => (value ? undefined : "Required"); return ( <>
{touched && error && ( Error: {" "} {errorMsg} )}
{Object.keys(options).map((key, index) => (
))}
); }; export const RenderRadioListWithText = ({ datafieldname, name, label, id, errorMsg, options, input: { onChange, value }, meta: { touched, error }, ...custom }) => { const required = (value) => (value ? undefined : "Required"); const router = useRouter(); return ( <>
{touched && error && ( Error: {" "} {errorMsg} )}
{Object.keys(options).map((key, index) => (
{value === options[key] && (
)}
))}
); }; export const RenderCondtionalRadioList = ({ datafieldname, legend, name, label, id, errorMsg, options, input: { onChange, value }, meta: { touched, error }, ...custom }) => { const required = (value) => (value ? undefined : "Required"); let { t } = useTranslation(); return ( <>
{touched && error && ( Error: {" "} {errorMsg} )}
{custom.hint}
{Object.keys(options).map((key, index) => (
{value == "Yes" && options[key] == "Yes" && (
)}
))}
); }; export const RenderLPACondtionalRadioList = ({ datafieldname, legend, name, label, id, errorMsg, options, input: { onChange, value }, meta: { touched, error }, ...custom }) => { const required = (value) => (value ? undefined : "Required"); let { t } = useTranslation(); return ( <>
{touched && error && ( Error: {" "} {errorMsg} )} {typeof custom.hint != undefined && (
{custom.hint}
)}
{Object.keys(options).map((key, index) => (
{value == custom.optionSelect && options[key] == custom.optionSelect && (
)}
))}
); }; export const RenderPickList = ({ datafieldname, name, label, input, optionsArr, meta: { touched, error }, errorMsg, ...custom }) => { // console.log(custom.disabled); let { t } = useTranslation(); return ( <>
{touched && error && ( Error:{" "} {errorMsg} )}
); }; export const RenderTextfield = ({ id, rows, datafieldname, name, label, input, errorMsg, type, className, meta: { touched, error }, ...custom }) => { const required = (value) => (value ? undefined : "Required"); return ( <>
{touched && error && ( Error:{" "} {errorMsg} )}
); }; export const RenderDatePicker = ({ datafieldname, name, label, id, errorMsg, input: { onChange, value }, meta: { touched, error }, ...custom }) => { const router = useRouter(); var startDateArr = custom.dateStart != "0" ? custom.dateStart.split("") : custom.dateStart; var endDateArr = custom.dateEnd != "0" ? custom.dateEnd.split("") : custom.dateEnd; var minDate = startDateArr == 0 ? new Date() : startDateArr[0] == "-" ? startDateArr[2] == "y" ? subYears(new Date(), startDateArr[1]) : startDateArr[2] == "m" ? subMonths(new Date(), startDateArr[1]) : subDays(new Date(), startDateArr[1]) : startDateArr[2] == "y" ? addYears(new Date(), startDateArr[1]) : startDateArr[2] == "m" ? addMonths(new Date(), startDateArr[1]) : addDays(new Date(), startDateArr[1]); var maxDate = endDateArr == 0 ? new Date() : startDateArr[0] == "-" ? startDateArr[2] == "y" ? subYears(new Date(), startDateArr[1]) : startDateArr[2] == "m" ? subMonths(new Date(), startDateArr[1]) : subDays(new Date(), startDateArr[1]) : startDateArr[2] == "y" ? addYears(new Date(), startDateArr[1]) : startDateArr[2] == "m" ? addMonths(new Date(), startDateArr[1]) : addDays(new Date(), startDateArr[1]); return ( <>
{touched && error && ( Error: {" "} {errorMsg} )}
); }; export const RenderMultiline = ({ id, className, rows, datafieldname, name, label, input, meta: { touched, error }, ...custom }) => { const [editValue, setEditValue] = useState( input.value != null ? input.value : "" ); const changeEdit = (valStr) => { setEditValue(valStr); input.onChange(valStr); }; const handleBlur = () => { const updated = updateLinks(editValue); setEditValue(updated); input.onChange(updated); }; var modules = { toolbar: [ [{ "header": [1, 2, false] }], ["bold", "italic", "underline", "blockquote"], [{ "list": "ordered" }, { "list": "bullet" }], ["clean"] ] }; return ( <>
{"hint" in custom} {"hint" in custom && (
{custom.hint}
)}
); }; export const RenderRichMultiline = ({ id, className, rows, datafieldname, name, label, input, meta: { touched, error }, ...custom }) => { const [editValue, setEditValue] = useState( input.value != null ? input.value : "" ); const changeEdit = (valStr) => { setEditValue(valStr); input.onChange(valStr); }; const handleBlur = () => { const updated = updateLinks(editValue); setEditValue(updated); input.onChange(updated); }; var modules = { toolbar: [ [{ "header": [1, 2, false] }], ["bold", "italic", "underline", "blockquote"], [{ "list": "ordered" }, { "list": "bullet" }], ["clean"] ] }; return ( <> ); }; export function MultiLinefield(props) { const { name, label } = props; return (
Do not include personal or financial information, like your National Insurance number or credit card details.
); } const baseStyle = { display: "flex", flexDirection: "column", alignItems: "center", padding: "20px", borderWidth: 2, borderRadius: 2, borderColor: "#eeeeee", borderStyle: "dashed", backgroundColor: "#fafafa", color: "#bdbdbd", transition: "border .3s ease-in-out" }; const activeStyle = { borderColor: "#2196f3" }; const acceptStyle = { borderColor: "#00e676" }; const rejectStyle = { borderColor: "#ff1744" }; export const RenderFileUpload = (field) => { const files = field.input.value; const dispatch = useDispatch(); // Get dispatch using useDispatch hook let { t } = useTranslation(); const style = useMemo( () => ({ ...baseStyle // ...(isDragActive ? activeStyle : {}), // ...(isDragAccept ? acceptStyle : {}), // ...(isDragReject ? rejectStyle : {}), }), [] // [isDragActive, isDragReject, isDragAccept] ); const [filesList, setFilesList] = useState([]); const thumbs = filesList.map((file) => (
{file.name} -{" "} {file.name} ({file.size / 1024}kb)
)); const getThumbnailIcon = (fileObj, fileType) => { switch (fileType) { case "text/html": return "/assets/images/documenttypes/html.png"; break; case "text/plain": return "/assets/images/documenttypes/txt.png"; break; case "application/msword": return "/assets/images/documenttypes/doc.png"; break; case "application/pdf": return "/assets/images/documenttypes/pdf.png"; break; case "application/vnd.openxmlformats-officedocument.wordprocessingml.document": return "/assets/images/documenttypes/docx.png"; break; case "text/csv": return "/assets/images/documenttypes/csv.png"; break; case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": return "/assets/images/documenttypes/xlsx.png"; break; case "image/jpeg": return "/assets/images/documenttypes/jpg.png"; case "image/png": return "/assets/images/documenttypes/jpg.png"; //return URL.createObjectURL(fileObj); break; default: return "/assets/images/documenttypes/default.png"; break; } }; const filelistObj = field.fileList || {}; let blobList = filelistObj; //let blobList = field.props.currentView.fileList; function removeNullObjects(arr) { if (arr.length === 0) { return arr; } return arr.filter((obj) => obj !== null); } function removeObjectsWithUndefinedKey(arr, key) { if (arr.length === 0) { return arr; } return arr.filter((obj) => obj[key] !== undefined); } function removeEmptyObjects(arr) { if (arr.length === 0) { return arr; } return arr.filter( (obj) => obj && typeof obj === "object" && Object.keys(obj).length > 0 ); } //blobList = removeEmptyObjects(blobList); //blobList = removeObjectsWithUndefinedKey(blobList, "name"); //blobList = removeNullObjects(blobList); let buildAppealFilesArray = []; let filesUploadObj = field.input.value; for (let key in filesUploadObj) { typeof filesUploadObj[key].name != "undefined" && buildAppealFilesArray.push({ "name": filesUploadObj[key].name, "size": filesUploadObj[key].size }); } //buildAppealFilesArray = buildAppealFilesArray.concat(blobList); function removeDuplicates(arr) { const seen = new Set(); return arr.filter((obj) => { const serializedObj = JSON.stringify(obj); if (seen.has(serializedObj)) { return false; // Skip this object (duplicate) } seen.add(serializedObj); // Add the serialized object to the "seen" set return true; // Keep this object }); } buildAppealFilesArray = removeDuplicates(buildAppealFilesArray); //blobList = buildAppealFilesArray; const deleteThisBlob = async ( containerName, blobName, deleteblobhash, getblobshash, casefolderID, filenamePrefix ) => { deleteRepBlob( containerName, blobName, deleteblobhash, casefolderID, filenamePrefix ) .then((data) => data) .then(() => { getFilesFromBlobHashed( containerName, getblobshash, casefolderID + "/" + filenamePrefix ).then((newfilelist) => field.setFilesForRepresentations(newfilelist.value[0]) ); }); }; const [errorMessage, setErrorMessage] = useState(null); const [rejectedFiles, setRejectedFiles] = useState([]); // Store rejected files const [completedUploadFiles, setCompletedUploadFiles] = useState(false); const [uploadCountMessage, setUploadCountMessage] = useState(""); const handleDropRejected = (fileRejections) => { const errors = fileRejections .map(({ file, errors }) => { return errors.map((error) => { if (error.code === "file-too-large") { return `${file.name} ${t( "myrepresentations:fileupload-file-error-filesize-label" )}`; } if (error.code === "file-invalid-type") { return `${file.name} ${t( "myrepresentations:fileupload-file-error-invalid-type-label" )}`; } if (error.code === "filename-invalid-chars") { return ( error.message || `${file.name} ${t( "newappeal:new-appeal-fileupload-file-error-invalid-filename-label" )}` ); } return `${file.name} ${t( "myrepresentations:fileupload-file-error-invalid-label" )}`; }); }) .flat(); // Flatten the errors array setRejectedFiles(errors); // Store the error messages for rejected files }; // Handle accepted files (clear error message when files are accepted) const handleDropAccepted = () => { setErrorMessage(null); setRejectedFiles([]); // Clear any previously rejected file errorsss }; const FILENAME_ALLOWED = /^[A-Za-z0-9 ._\-:()—']+$/; const validateFilename = (file, t) => { const name = file.name; // block # if (name.includes("#")) { return { code: "filename-invalid-chars", message: `${name} ${t( "myrepresentations:fileupload-file-error-invalid-filename-label" )}` }; } // broader blocklist (optional but recommended) if (!FILENAME_ALLOWED.test(name)) { return { code: "filename-invalid-chars", message: `${name} ${t( "myrepresentations:fileupload-file-error-invalid-filename-label" )}` }; } // block characters < > " / \ | ? * if (/[<>"/\\|?*]/.test(name)) { return { code: "filename-invalid-chars", message: `${name} ${t( "myrepresentations:fileupload-file-error-invalid-filename-label" )}` }; } return null; // valid }; return ( <> validateFilename(file, t)} onDrop={(acceptedFiles, fileRejections, e) => { // build + display rejected messages (ALWAYS, even if some accepted) if (fileRejections?.length) { handleDropRejected(fileRejections); } else { // only clear rejections when nothing was rejected on this drop setRejectedFiles([]); setErrorMessage(null); } // if nothing accepted, stop (prevents showing only "uploading" when all invalid) if (!acceptedFiles || acceptedFiles.length === 0) { setCompletedUploadFiles(true); // optional: depends on your UX setUploadCountMessage(0); return; } setCompletedUploadFiles(false); const renamedAcceptedFiles = acceptedFiles.map( (file) => new File( [file], `${ field.filenamePrefix.hasOwnProperty( "representationForm" ) && field.filenamePrefix.representationForm.hasOwnProperty( "values" ) && field.filenamePrefix.representationForm.values.hasOwnProperty( "repfile_name" ) && field.filenamePrefix.representationForm .values.repfile_name }_-_Attachment_-_${file.name}`, { type: file.type } ) ); field.input.onChange(renamedAcceptedFiles); if (typeof field.setFileCount === "function") { field.setFileCount( (field.uploadCount || 0) + renamedAcceptedFiles.length ); } setUploadCountMessage(renamedAcceptedFiles.length); uploadSingleFile( renamedAcceptedFiles, field.containerID, field.ticketnumber + "/" + field.filenamePrefix.representationForm.values .repfile_name ).then((data) => { console.log(data); let buildAppealFilesArray = []; let values = field; let filesUploadObj = renamedAcceptedFiles; for (let key in filesUploadObj) { typeof filesUploadObj[key].name != "undefined" && buildAppealFilesArray.push({ "name": filesUploadObj[key].name, "size": filesUploadObj[key].size }); } //console.log(buildAppealFilesArray); function removeDuplicates(arr, key) { const seen = new Set(); return arr.filter((item) => { const value = item[key]; if (seen.has(value)) { return false; // Duplicate found } else { seen.add(value); // Add value to the set return true; // Keep the item } }); } buildAppealFilesArray = values.hasOwnProperty( "fileList" ) ? buildAppealFilesArray.concat(values.fileList) : buildAppealFilesArray; buildAppealFilesArray = removeDuplicates( buildAppealFilesArray, "name" ); // Object.assign(values, { // "fileList": buildAppealFilesArray, // }); data.invalidFiles.length > 0 && (setRejectedFiles(data.invalidFiles), setUploadCountMessage( renamedAcceptedFiles.length - data.invalidFiles.length )); getFilesFromBlobproxy( field.containerID, field.ticketnumber + "/" + (typeof field.filenamePrefix != "object" ? field.filenamePrefix : encodeURIComponent( field.filenamePrefix .representationForm.values .repfile_name )) ).then((data) => { data = data.value || {}; field.input.value = ""; field.setFilesForRepresentations(data[0]); setCompletedUploadFiles(true); dispatch( change("representationForm", field.name, null) ); // Set the field value to null }); }); }} > {({ getRootProps, getInputProps }) => ( <>
{" "}
{t( "myrepresentations:fileupload-drop-label" )} (Max 50 MB)
( {t( "myrepresentations:fileupload-file-list-label" )} )
{/* */}
)}
{rejectedFiles.length > 0 && (
    {rejectedFiles.map((error, index) => (
  • {error}
  • ))}
)} {uploadCountMessage > 0 && completedUploadFiles == false && (
{t("home:uploading-files-label", { number: uploadCountMessage })}
)} {completedUploadFiles > 0 && uploadCountMessage >= 0 && (
{t("home:completed-uploading-files-label", { number: uploadCountMessage })}
)} {field.meta.touched && field.meta.error && ( {field.meta.error} )} {!completedUploadFiles && filesUploadObj && Array.isArray(filesUploadObj) && filesUploadObj.map( (file, i) => typeof file.name != "undefined" && (
{file.name} {file.name} ({bytesToSize(file.size)} )
{completedUploadFiles ? (

Upload complete

) : (

{t( "home:uploading-files-progress-label" )}

)}
) )} {blobList.length > 0 && (

{t("myrepresentations:previously-added-files")}

)} {blobList.map((blob, i) => (
{ setUploadCountMessage(uploadCountMessage - 1); deleteThisBlob( field.containerID, blob.name, blob.hasheddeletepath, blob.hashgetblobs, field.ticketnumber, blob.path.split("/")[1] ); }} title={t("home:remove-this-file-label")} > − {blob.name} {blob.name} ( {bytesToSize(blob.contentLength || blob.size)})
))}{" "} ); }; export function FileUploadField(props) { return ( <>

{props.label}

); } export const shouldDisableButton = ({ props, formObj }) => !( props?.currentView?.fileList?.length > 0 || typeof formObj?.representationForm?.values?.representationComments !== "undefined" ); // export const RepresentationGuidanceText = (appealType) => { // let { t } = useTranslation(); // console.log(appealType); // switch (appealType) { // case "846040004": //has/cas // return ( //

// {t("myrepresentations:questionnaire-publish-policy-label")}{" "} // // https://gov.wales/planning-and-environment-decisions-wales-publishing-policy-html // //

// ); // break; // default: // return ( //

// {t("myrepresentations:publish-policy-label")}{" "} // // https://gov.wales/planning-and-environment-decisions-wales-publishing-policy-html // //

// ); // break; // } // return guidanceString; // };