Files
pedwfrontend/components/elements/index.js
T

2694 lines
99 KiB
JavaScript

import { JSONPath as jsonpath } from "jsonpath-plus";
import axios from "axios";
import _ from "lodash";
import useTranslation from "next-translate/useTranslation";
import Link from "next/link";
import { useRouter } from "next/router";
import React, { useMemo, useState } from "react";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import Dropzone from "react-dropzone";
import { Field, FieldArray, change } from "redux-form";
import {
deleteBlob,
getFilesFromBlobHashed,
uploadSingleFile,
getFilesFromBlobproxy
} from "../../actions";
import fieldLookup from "../../data/crmfieldlookuptranslations.json";
import pickListLookup from "../../data/picklistLookups.json";
import dynamic from "next/dynamic";
import { bytesToSize, getThumbnailIconByExtension } from "../utils";
import { setFileCount } from "../../store/appealType/action";
import {
addDays,
addMonths,
addYears,
parseISO,
subDays,
subMonths,
subYears
} from "date-fns";
import "react-quill-new/dist/quill.snow.css";
const ReactQuill = dynamic(() => import("react-quill-new"), { ssr: false });
import { useStore as store, useSelector } from "react-redux";
import { useDispatch } from "react-redux";
import { validateField } from "./validationUtils"; // Import the validation function
const RenderTextfield = ({
id,
className,
rows,
datafieldname,
name,
label,
input,
errorMsg,
meta: { touched, error },
...custom
}) => {
return (
<>
<div
className={
touched && error
? "govuk-form-group govuk-form-group--error"
: "govuk-form-group "
}
>
<label className="govuk-label " htmlFor={id} id={id + "_label"}>
{FieldsTranslations(label)}
</label>
{!custom.hasOwnProperty("showErrorBottom")
? touched &&
error && (
<span
id={id + "-error"}
className="govuk-error-message"
>
<span className="govuk-visually-hidden">
Error:
</span>{" "}
{error}
</span>
)
: ""}
<input
{...input}
type={custom.type}
className={className}
name={name}
id={id}
maxLength={
custom.hasOwnProperty("maxFieldLength")
? custom.maxFieldLength
? custom.maxFieldLength
: 100
: 200
}
pattern={
custom.hasOwnProperty("pattern")
? custom.pattern
: undefined
}
/>
{custom.hasOwnProperty("showErrorBottom")
? touched &&
error && (
<span
id={id + "-error"}
className="govuk-error-message"
>
<span className="govuk-visually-hidden">
Error:
</span>{" "}
{error}
</span>
)
: ""}
</div>
</>
);
};
export function Textfield(props) {
const {
name,
label,
validation,
ticketnumber,
form,
formProps,
parentField,
parentFieldShowOnValue,
maxFieldLength,
hint
} = props;
const required = (value) => {
let errors;
// Check for the required field
if (!value) {
errors = t("newappeal:is-required-label");
} else {
// Check for emojis using a regular expression
const emojiRegex =
/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F700}-\u{1F77F}\u{1F780}-\u{1F7FF}\u{1F800}-\u{1F8FF}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{2300}-\u{23FF}\u{2B50}\u{1F004}-\u{1F0CF}\u{1F0A0}-\u{1F0A5}\u{1F170}-\u{1F251}]/gu;
if (emojiRegex.test(value)) {
errors = "Emojis are not allowed";
}
}
return errors;
};
const postcode = (value) =>
value &&
!/^([A-Z][A-HJ-Y]?[0-9][A-Z0-9]? ?[0-9][A-Z]{2}|GIR ?0A{2})$/i.test(
value
)
? t("newappeal:invalid-postcode-label")
: undefined;
let { t } = useTranslation();
if (name == "pinswg_name") {
return (
<>
<div className="govuk-form-group">
<label className="govuk-label " htmlFor={props.name}>
{FieldsTranslations(label)}
</label>
<input
type="text"
name={name}
id={props.name}
//component={RenderCaseID}
value={ticketnumber}
className="govuk-input govuk-input--width-20"
disabled
/>
</div>
<div className="govuk-body">
<p>{t("newappeal:new-appeal-introduction-paragraph")}</p>
<ul>
<li>
{t(
"newappeal:new-appeal-introduction-paragraph-bullet-one"
)}
</li>
<li>
{t(
"newappeal:new-appeal-introduction-paragraph-bullet-two"
)}
</li>
</ul>
<p>
{t("newappeal:new-appeal-introduction-paragraph-two")}
</p>
<p>
{t(
"newappeal:new-appeal-introduction-warning-paragraph"
)}
</p>
<ul>
<li>
{t(
"newappeal:new-appeal-introduction-warning-bullet-one"
)}
</li>
<li>
{t(
"newappeal:new-appeal-introduction-warning-bullet-two"
)}
</li>
</ul>
</div>
</>
);
} else {
var showIfHasParentShowValue =
(parentField != false &&
!_.isEmpty(formProps[form]) &&
_.has(formProps[form].values, parentField) &&
formProps[form].values[parentField]) == parentFieldShowOnValue;
// console.log(parentField, showIfHasParentShowValue);
// console.log(parentField, formProps, form);
//console.log("validation props:", validation);
const requiredMessage = t("newappeal:is-required-label");
const emojiNotAllowedMessage = t("newappeal:emojis-not-allowed-label");
const invalidPostcodeMessage = t("newappeal:invalid-postcode-label");
return (
<div className="govuk-form-group">
{parentField != false ? (
showIfHasParentShowValue && (
<Field
name={props.name}
id={props.name}
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
validate={(value) =>
validateField(
value,
validation,
requiredMessage,
emojiNotAllowedMessage,
invalidPostcodeMessage
)
} // Use the external validate function
label={props.label}
maxFieldLength={props.maxFieldLength}
/>
)
) : (
<Field
name={props.name}
id={props.name}
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
validate={(value) =>
validateField(
value,
validation,
requiredMessage,
emojiNotAllowedMessage,
invalidPostcodeMessage
)
} // Use the external validate function
label={props.label}
errorMsg={t("newappeal:is-required-label")}
maxFieldLength={props.maxFieldLength}
/>
)}
</div>
);
}
}
const RenderMultiline = ({
id,
className,
rows,
datafieldname,
name,
label,
input,
errorMsg,
meta: { touched, error },
...custom
}) => {
return (
<>
<div
className={
touched && error
? "govuk-form-group govuk-form-group--error"
: "govuk-form-group "
}
>
{/* <label className="govuk-label " htmlFor={id} id={id + "_label"}>
{FieldsTranslations(label)}s
</label> */}
{touched && error && (
<span id={id + "-error"} className="govuk-error-message">
<span className="govuk-visually-hidden">Error:</span>{" "}
{touched &&
((error && (
<span>{errorMsg ? errorMsg : error}</span>
)) ||
(warning && <span>{warning}</span>))}
</span>
)}
<textarea
id={id}
name={id}
rows={rows}
className={className}
{...input}
maxLength={
custom.hasOwnProperty("maxFieldLength")
? custom.maxFieldLength
? custom.maxFieldLength
: 800
: 800
}
></textarea>
</div>
</>
);
};
export function MultiLinefield(props) {
const {
name,
label,
form,
formProps,
parentField,
parentFieldShowOnValue,
validation,
maxFieldLength,
hint
} = props;
const required = (value) => {
let errors;
// Check for the required field
if (!value) {
errors = t("newappeal:is-required-label");
} else {
// Check for emojis using a regular expression
const emojiRegex =
/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F700}-\u{1F77F}\u{1F780}-\u{1F7FF}\u{1F800}-\u{1F8FF}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{2300}-\u{23FF}\u{2B50}\u{1F004}-\u{1F0CF}\u{1F0A0}-\u{1F0A5}\u{1F170}-\u{1F251}]/gu;
if (emojiRegex.test(value)) {
errors = t("newappeal:emojis-not-allowed-label");
}
}
return errors;
};
var showIfHasParentShowValue =
parentField != false &&
!_.isEmpty(formProps[form]) &&
_.has(formProps[form].values, parentField) &&
parentFieldShowOnValue.indexOf(
formProps[form].values[parentField].toString()
) > -1;
(showIfHasParentShowValue == parentField) != false &&
showIfHasParentShowValue;
//console.log(showIfHasParentShowValue, formProps[form].values[parentField]);
let { t } = useTranslation();
const requiredMessage = t("newappeal:is-required-label");
const emojiNotAllowedMessage = t("newappeal:emojis-not-allowed-label");
const invalidPostcodeMessage = t("newappeal:invalid-postcode-label");
return (
<>
{parentField != false ? (
showIfHasParentShowValue && (
<div>
<label className="govuk-label" htmlFor={props.name}>
{FieldsTranslations(props.label)}
</label>
<div
id={props.name + "-hint"}
className="govuk-hint govuk-!-font-size-14"
>
{props.hasOwnProperty("hint") &&
props.hint != false &&
t(props.hint)}{" "}
<br />
{t("newappeal:new-appeal-multiline-pid")}
</div>
<Field
name={props.name}
id={props.name}
component={RenderMultiline}
type="text"
rows="5"
className="govuk-textarea govuk-input--width-30"
aria-describedby={props.name + "-hint"}
validate={(value) =>
validateField(
value,
validation,
requiredMessage,
emojiNotAllowedMessage,
invalidPostcodeMessage
)
} // Use the external validate function
maxFieldLength={props.maxFieldLength}
/>
</div>
)
) : (
<div className="govuk-form-group">
<label className="govuk-label" htmlFor={props.name}>
{FieldsTranslations(props.label)}
</label>
<div
id={props.name + "-hint"}
className="govuk-hint govuk-!-font-size-14"
>
{props.hasOwnProperty("hint") &&
props.hint != false &&
t(props.hint)}{" "}
<br />
{t("newappeal:new-appeal-multiline-pid")}
</div>
<Field
name={props.name}
id={props.name}
component={RenderMultiline}
type="text"
rows="5"
className="govuk-textarea govuk-input--width-30"
aria-describedby={props.name + "-hint"}
validate={(value) =>
validateField(
value,
validation,
requiredMessage,
emojiNotAllowedMessage,
invalidPostcodeMessage
)
} // Use the external validate function
maxFieldLength={props.maxFieldLength}
/>
</div>
)}
</>
);
}
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);
};
var modules = {
toolbar: [
[{ "header": [1, 2, false] }],
["bold", "italic", "underline", "blockquote"],
[{ "list": "ordered" }, { "list": "bullet" }],
["clean"]
]
};
return (
<>
<div
className={
touched && error
? "govuk-form-group govuk-form-group--error"
: "govuk-form-group "
}
>
<label className="govuk-label" htmlFor={name}>
{label}
</label>
{error && (
<span id={id + "-error"} className="govuk-error-message">
<span className="govuk-visually-hidden">Error:</span>{" "}
{error}
</span>
)}
<ReactQuill
modules={modules}
theme="snow"
value={editValue}
onChange={changeEdit}
/>
<textarea
{...input}
id={id}
name={name}
rows={rows}
className={className}
value={editValue}
style={{ display: "none" }}
></textarea>
</div>
</>
);
};
export function RichMultiLinefield(props) {
const {
name,
label,
form,
formProps,
parentField,
parentFieldShowOnValue,
validation,
maxFieldLength
} = props;
const required = (value) =>
value ? undefined : t("newappeal:is-required-label");
var showIfHasParentShowValue =
parentField != false &&
!_.isEmpty(formProps[form]) &&
_.has(formProps[form].values, parentField) &&
parentFieldShowOnValue.indexOf(
formProps[form].values[parentField].toString()
) > -1;
(showIfHasParentShowValue == parentField) != false &&
showIfHasParentShowValue;
//console.log(showIfHasParentShowValue, formProps[form].values[parentField]);
let { t } = useTranslation();
const requiredMessage = t("newappeal:is-required-label");
const emojiNotAllowedMessage = t("newappeal:emojis-not-allowed-label");
const invalidPostcodeMessage = t("newappeal:invalid-postcode-label");
return (
<>
{parentField != false ? (
showIfHasParentShowValue && (
<div>
<label className="govuk-label" htmlFor={props.name}>
{FieldsTranslations(props.label)}
</label>
<div
id={props.name + "-hint"}
className="govuk-hint govuk-!-font-size-14"
>
{props.hasOwnProperty("hint") &&
props.hint != false &&
t(props.hint)}{" "}
<br />
{t("newappeal:new-appeal-multiline-pid")}
</div>
<Field
name={props.name}
id={props.name}
component={RenderRichMultiline}
type="text"
rows="5"
className="govuk-textarea govuk-input--width-30"
aria-describedby={props.name + "-hint"}
validate={(value) =>
validateField(
value,
validation,
requiredMessage,
emojiNotAllowedMessage,
invalidPostcodeMessage
)
} // Use the external validate function
maxFieldLength={props.maxFieldLength}
/>
</div>
)
) : (
<div className="govuk-form-group">
<label className="govuk-label" htmlFor={props.name}>
{FieldsTranslations(props.label)}
</label>
<div
id={props.name + "-hint"}
className="govuk-hint govuk-!-font-size-14"
>
{props.hasOwnProperty("hint") &&
props.hint != false &&
t(props.hint)}{" "}
<br />
{t("newappeal:new-appeal-multiline-pid")}
</div>
<Field
name={props.name}
id={props.name}
component={RenderRichMultiline}
type="text"
rows="5"
className="govuk-textarea govuk-input--width-30"
aria-describedby={props.name + "-hint"}
validate={(value) =>
validateField(
value,
validation,
requiredMessage,
emojiNotAllowedMessage,
invalidPostcodeMessage
)
} // Use the external validate function
maxFieldLength={props.maxFieldLength}
/>
</div>
)}
</>
);
}
const RenderDatePicker = ({
datafieldname,
name,
label,
id,
errorMsg,
input: { onChange, value },
meta: { touched, error },
...custom
}) => {
const router = useRouter();
let { t } = useTranslation();
function dateFromOffset(offsetStr) {
const today = new Date();
// "0" means today
if (!offsetStr || offsetStr === "0") {
return today;
}
// Expect formats like: -5d, +18m, -10y
const match = offsetStr.match(/^([+-])(\d+)([dmy])$/i);
if (!match) {
console.warn("Invalid offset format:", offsetStr);
return today; // fallback
}
const [, sign, numberStr, unitRaw] = match;
const amount = parseInt(numberStr, 10);
const unit = unitRaw.toLowerCase();
const isNegative = sign === "-";
switch (unit) {
case "y":
return isNegative
? subYears(today, amount)
: addYears(today, amount);
case "m":
return isNegative
? subMonths(today, amount)
: addMonths(today, amount);
case "d":
return isNegative
? subDays(today, amount)
: addDays(today, amount);
default:
return today;
}
}
const minDate = dateFromOffset(custom.dateStart);
const maxDate = dateFromOffset(custom.dateEnd);
return (
<>
<div
className={
touched && error
? "govuk-form-group govuk-form-group--error"
: "govuk-form-group "
}
>
<fieldset
className="govuk-fieldset"
role="group"
aria-describedby={id + "_label"}
>
<legend className="govuk-fieldset__legend govuk-fieldset__legend--s govuk-!-font-weight-regular">
<label
className="govuk-fieldset__heading"
id={id + "_label"}
htmlFor={id}
>
{FieldsTranslations(label)}
</label>
{custom.hasOwnProperty("hint") && (
<div className="govuk-hint govuk-!-font-size-14">
{t(custom.hint)}
</div>
)}
</legend>
{!custom.hasOwnProperty("showErrorBottom")
? touched &&
error && (
<span
id={id + "-error"}
className="govuk-error-message"
>
<span className="govuk-visually-hidden">
Error:
</span>{" "}
{errorMsg}
</span>
)
: ""}
{/* {value}
<br />
{moment(value).format("DD/MM/YY")}
<br /> */}
<DatePicker
{...custom}
locale={router.locale}
autoOk={true}
id={id}
dateFormat="dd/MM/yyyy"
onChange={onChange}
selected={
value
? typeof value != "object"
? parseISO(value)
: value
: null
} //{parseISO(value) || null}
className="govuk-input govuk-input--width-10"
minDate={minDate}
maxDate={maxDate}
autoComplete="off"
//value={!value ? null : moment(value, "DD/MM/yyyy")}
/>
{custom.hasOwnProperty("showErrorBottom")
? touched &&
error && (
<span
id={id + "-error"}
className="govuk-error-message"
>
<span className="govuk-visually-hidden">
Error:
</span>{" "}
{error}
</span>
)
: ""}
</fieldset>
</div>
</>
);
};
export function DateFieldPicker(props) {
const {
name,
label,
parentField,
form,
formProps,
parentFieldShowOnValue,
validation
} = props;
let dateStart = props.dateStart;
let dateEnd = props.dateEnd;
let { t } = useTranslation();
const required = (value) => (value ? undefined : "Required");
var showIfHasParentShowValue =
(parentField != false &&
!_.isEmpty(formProps[form]) &&
_.has(formProps[form].values, parentField) &&
formProps[form].values[parentField]) == parentFieldShowOnValue;
const requiredMessage = t("newappeal:is-required-label");
const emojiNotAllowedMessage = t("newappeal:emojis-not-allowed-label");
const invalidPostcodeMessage = t("newappeal:invalid-postcode-label");
// a and b are javascript Date objects
function dateDiffInDays(a, b) {
const _MS_PER_DAY = 1000 * 60 * 60 * 24;
// Discard the time and time-zone information.
const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
const utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
return Math.floor((utc2 - utc1) / _MS_PER_DAY);
}
dateStart =
showIfHasParentShowValue && name == "pinswg_dateoflpadecision"
? "-" +
(dateDiffInDays(
new Date(formProps[form].values["pinswg_dateofapplication"]),
new Date()
) -
1) +
"d"
: dateStart;
return (
<div className="govuk-form-group">
{parentField != false ? (
showIfHasParentShowValue && (
<Field
name={name}
id={name}
label={label}
component={RenderDatePicker}
validate={(value) =>
validateField(
value,
validation,
requiredMessage,
emojiNotAllowedMessage,
invalidPostcodeMessage
)
} // Use the external validate function
errorMsg={t("newappeal:select-a-date-label")}
dateStart={dateStart}
dateEnd={dateEnd}
hint={props.hint}
/>
)
) : (
<Field
name={name}
id={name}
label={label}
component={RenderDatePicker}
validate={(value) =>
validateField(
value,
validation,
requiredMessage,
emojiNotAllowedMessage,
invalidPostcodeMessage
)
} // Use the external validate function
errorMsg={t("newappeal:select-a-date-label")}
dateStart={dateStart}
dateEnd={dateEnd}
hint={props.hint}
/>
)}
</div>
);
}
export function DateField(props) {
const { name, label } = props;
return (
<div className="govuk-form-group">
<fieldset
className="govuk-fieldset"
role="group"
aria-describedby={name + "-hint"}
>
<legend className="govuk-fieldset__legend govuk-fieldset__legend--s govuk-!-font-weight-regular">
<label className="govuk-fieldset__heading">
{FieldsTranslations(props.label)}
</label>
</legend>
<div className="govuk-date-input" id={name}>
<div className="govuk-date-input__item">
<div className="govuk-form-group">
<label
className="govuk-label govuk-date-input__label govuk-!-font-size-14"
htmlFor={props.name + "-day"}
>
Day
</label>
<Field
name={props.name + "-day"}
id={props.name + "-day"}
component="input"
type="text"
pattern="[0-9]*"
inputMode="numeric"
className="govuk-input govuk-date-input__input govuk-input--width-2"
/>
</div>
</div>
<div className="govuk-date-input__item">
<div className="govuk-form-group">
<label
className="govuk-label govuk-date-input__label govuk-!-font-size-14"
htmlFor={props.name + "-month"}
>
Month
</label>
<Field
name={props.name + "-month"}
id={props.name + "-month"}
component="input"
type="text"
pattern="[0-9]*"
inputMode="numeric"
className="govuk-input govuk-date-input__input govuk-input--width-2"
/>
</div>
</div>
<div className="govuk-date-input__item">
<div className="govuk-form-group">
<label
className="govuk-label govuk-date-input__label govuk-!-font-size-14"
htmlFor={props.name + "-year"}
>
Year
</label>
<Field
name={props.name + "-year"}
id={props.name + "-year"}
component="input"
type="text"
pattern="[0-9]*"
inputMode="numeric"
className="govuk-input govuk-date-input__input govuk-input--width-4"
/>
</div>
</div>
</div>
</fieldset>
</div>
);
}
const RenderYesNo = ({
datafieldname,
name,
label,
id,
errorMsg,
options,
input: { onChange, value },
meta: { touched, error },
...custom
}) => {
return (
<>
<div
className={
touched && error
? "govuk-form-group govuk-form-group--error"
: "govuk-form-group "
}
>
<fieldset
className="govuk-fieldset"
aria-describedby={id + "_label"}
>
<legend className="govuk-fieldset__legend govuk-fieldset__legend--s govuk-!-font-weight-regular">
<label
className="govuk-fieldset__heading govuk-body-m"
id={id + "_label"}
>
{FieldsTranslations(label)}
</label>
</legend>
{touched && error && (
<span
id={id + "-error"}
className="govuk-error-message"
>
<span className="govuk-visually-hidden">
Error:
</span>{" "}
{errorMsg}
</span>
)}
<div
className={
custom.inline == false
? "govuk-radios govuk-radios--small"
: "govuk-radios govuk-radios--inline govuk-radios--small"
}
>
{Object.keys(options).map((key, index) => (
<div
className="govuk-radios__item"
data-children-count={key}
key={key}
>
<Field
id={id + "_" + index}
name={id}
component="input"
type="radio"
className="govuk-radios__input"
value={
options[key] == "Yes" ||
options[key] == "Ydw"
? true
: false
}
//checked={value == options[key]}
checked={
typeof value != "undefined" &&
value ==
(options[key] == "Yes" ||
options[key] == "Ydw"
? "true"
: "false")
}
onChange={(e) => {
let docListObj = custom.documentList;
if (
custom.requiredDocumentValue ==
"Yes" ||
custom.requiredDocumentValue ==
"Ydw" ||
custom.requiredDocumentValue ==
"true"
) {
e.target.value == "Ydw" ||
e.target.value == "Yes" ||
e.target.value == "true"
? (docListObj = Object.assign(
docListObj,
{
["documentCheckList_" +
id]:
custom.requiredDocumentLabel
}
))
: delete docListObj[
"documentCheckList_" + id
];
custom.setDocumentsList(docListObj);
}
}}
/>
<label
className="govuk-label govuk-radios__label"
htmlFor={id + "_" + index}
>
{options[key]}
</label>
</div>
))}
</div>
</fieldset>
</div>
</>
);
};
export function YesNofield(props) {
const router = useRouter();
let { t } = useTranslation();
const {
name,
label,
inline,
form,
formProps,
validation,
parentFieldShowOnValue,
parentField
} = props;
const yesNo = router.locale == "cy" ? ["Ydw", "Na"] : ["Yes", "No"];
const required = (value) => (value ? undefined : "Required");
var showIfHasParentShowValue =
parentField != false &&
!_.isEmpty(formProps[form]) &&
_.has(formProps[form].values, parentField) &&
parentFieldShowOnValue.indexOf(
formProps[form].values[parentField].toString()
) > -1;
(showIfHasParentShowValue == parentField) != false &&
showIfHasParentShowValue;
return (
<>
{parentField != false ? (
showIfHasParentShowValue && (
<Field
name={props.name}
datafieldname={props.name}
label={props.label}
options={yesNo}
id={props.name}
className={"govuk-radios__input"}
validate={[required]}
errorMsg={t("newappeal:select-an-option-label")}
component={RenderYesNo}
inline={_.has(props, "inline") ? props.inline : "true"}
requiredDocumentLabel={props.requiredDocumentLabel}
requiredDocumentValue={props.requiredDocumentValue}
setDocumentsList={props.setDocumentsList}
documentList={props.documentList}
/>
)
) : (
<Field
name={props.name}
datafieldname={props.name}
label={props.label}
options={yesNo}
id={props.name}
className={"govuk-radios__input"}
validate={[required]}
errorMsg={t("newappeal:select-an-option-label")}
component={RenderYesNo}
inline={_.has(props, "inline") ? props.inline : "true"}
requiredDocumentLabel={props.requiredDocumentLabel}
requiredDocumentValue={props.requiredDocumentValue}
setDocumentsList={props.setDocumentsList}
documentList={props.documentList}
/>
)}
</>
);
}
const RenderRadio = ({
datafieldname,
name,
label,
id,
errorMsg,
options,
input: { onChange, value },
meta: { touched, error },
...custom
}) => {
let { t } = useTranslation();
return (
<>
<div
className={
touched && error
? "govuk-form-group govuk-form-group--error"
: "govuk-form-group "
}
>
<fieldset
className="govuk-fieldset"
aria-describedby={id + "_label"}
>
<legend className="govuk-fieldset__legend govuk-fieldset__legend--s govuk-!-font-weight-regular">
<label
className="govuk-fieldset__heading govuk-body-m"
id={id + "_label"}
>
{FieldsTranslations(label)}
</label>
</legend>
{touched && error && (
<span
id={id + "-error"}
className="govuk-error-message"
>
<span className="govuk-visually-hidden">
Error:
</span>{" "}
{errorMsg}
</span>
)}
<div
className={
custom.inline == false
? "govuk-radios govuk-radios--small"
: "govuk-radios govuk-radios--inline govuk-radios--small"
}
>
{" "}
{custom.hint != false && (
<div className="govuk-hint">{t(custom.hint)}</div>
)}
{Object.keys(options).map((key, index) => (
<div
className="govuk-radios__item"
data-children-count={key}
key={key}
>
<Field
id={id + "_" + index}
name={id}
component="input"
type="radio"
className="govuk-radios__input"
value={options[key].split("|")[0]}
checked={
typeof value != "undefined" &&
value ==
parseInt(options[key].split("|")[0])
? true
: false
}
onChange={(e) => {
let docListObj = custom.documentList;
if (
custom.requiredDocumentValue ==
"Yes"
) {
e.target.value != "846040000"
? (docListObj = Object.assign(
docListObj,
{
["documentCheckList_" +
id]:
custom.requiredDocumentLabel
}
))
: delete docListObj[
"documentCheckList_" + id
];
custom.setDocumentsList(docListObj);
}
}}
/>
<label
className="govuk-label govuk-radios__label"
htmlFor={id + "_" + index}
>
{t(options[key].split("|")[1])}
</label>
</div>
))}
</div>
</fieldset>
</div>
</>
);
};
export function Radiofield(props) {
const {
name,
label,
datafieldname,
form,
formProps,
parentFieldShowOnValue,
parentField,
validation
} = props;
const router = useRouter();
const required = (value) => (value ? undefined : "Required");
// console.log(props.options);
let { t } = useTranslation();
const fieldOptions = props.options
.slice(1, props.options.length - 1)
.split(",");
//parentFieldShowOnValue
var showIfHasParentShowValue =
parentField != false &&
!_.isEmpty(formProps[form]) &&
_.has(formProps[form].values, parentField) &&
parentFieldShowOnValue.indexOf(
formProps[form].values[parentField].toString()
) > -1;
(showIfHasParentShowValue == parentField) != false &&
showIfHasParentShowValue;
const requiredMessage = t("newappeal:is-required-label");
const emojiNotAllowedMessage = t("newappeal:emojis-not-allowed-label");
const invalidPostcodeMessage = t("newappeal:invalid-postcode-label");
return (
<>
{parentField != false ? (
showIfHasParentShowValue && (
<Field
name={name}
datafieldname={datafieldname}
label={label}
options={fieldOptions}
id={name}
//validate={[required]}
errorMsg={t("newappeal:select-an-option-label")}
component={RenderRadio}
inline={props.inline}
hint={props.hint}
validate={(value) =>
validateField(
value,
validation,
requiredMessage,
emojiNotAllowedMessage,
invalidPostcodeMessage
)
} // Use the external validate function
requiredDocumentLabel={props.requiredDocumentLabel}
requiredDocumentValue={props.requiredDocumentValue}
setDocumentsList={props.setDocumentsList}
documentList={props.documentList}
/>
)
) : (
<Field
name={name}
datafieldname={datafieldname}
label={label}
options={fieldOptions}
id={name}
//validate={[required]}
errorMsg={t("newappeal:select-an-option-label")}
component={RenderRadio}
inline={props.inline}
hint={props.hint}
validate={(value) =>
validateField(
value,
validation,
requiredMessage,
emojiNotAllowedMessage,
invalidPostcodeMessage
)
} // Use the external validate function
requiredDocumentLabel={props.requiredDocumentLabel}
requiredDocumentValue={props.requiredDocumentValue}
setDocumentsList={props.setDocumentsList}
documentList={props.documentList}
/>
)}
</>
);
}
const RenderCheckBox = ({
datafieldname,
name,
label,
id,
errorMsg,
options,
input: { onChange, value },
meta: { touched, error },
...custom
}) => {
return (
<>
<div
className={
touched && error
? "govuk-form-group govuk-form-group--error"
: "govuk-form-group "
}
>
<fieldset
className="govuk-fieldset"
aria-describedby={name + "-hint"}
>
<legend className="govuk-fieldset__legend govuk-fieldset__legend--s govuk-!-font-weight-regular">
<label
className="govuk-fieldset__heading govuk-body-m"
id={id + "_label"}
>
{FieldsTranslations(label)}
</label>
</legend>
{touched && error && (
<span
id={id + "-error"}
className="govuk-error-message"
>
<span className="govuk-visually-hidden">
Error:
</span>{" "}
{errorMsg}
</span>
)}
<div className="govuk-checkboxes ">
{Object.keys(options).map((key, index) => (
<div
className="govuk-checkboxes__item"
data-children-count={key}
key={key}
>
<Field
id={id + "_" + index}
name={id + "_" + index}
component="input"
type="checkbox"
className="govuk-checkboxes__input"
value={options[key]}
/>
<label
className="govuk-label govuk-checkboxes__label"
htmlFor={id + "_" + index}
>
{options[key]}
</label>
</div>
))}
</div>
</fieldset>
</div>
</>
);
};
export function CheckBoxfield(props) {
const router = useRouter();
console.log(props);
const required = (value) => (value ? undefined : "Required");
const fieldOptions =
router.locale == "cy" ? ["Ydw", "Na", "2323"] : ["Yes", "No", "23232"];
return (
<Field
name={props.name}
datafieldname={props.datafieldname}
label={props.label}
options={fieldOptions}
id={props.id}
className={props.className}
//validate={[required]}
errorMsg={t("newappeal:select-an-option-label")}
component={RenderCheckBox}
/>
);
}
const RenderPickList = ({
datafieldname,
picklistData,
id,
name,
label,
input,
errorMsg,
meta: { touched, error }
}) => {
let { t } = useTranslation();
var dropdownObj = jsonpath({
path:
"$..value[?(@ && @.LogicalName=='" + datafieldname + "')]..Options",
json: picklistData,
eval: true
});
dropdownObj = dropdownObj[0];
const required = (value) => {
return value || value == 0
? undefined
: t("newappeal:is-required-label");
};
return (
<>
<div
className={
touched && error
? "govuk-form-group govuk-form-group--error"
: "govuk-form-group "
}
>
<label className="govuk-label " htmlFor={id} id={id + "_label"}>
{FieldsTranslations(label)}
</label>
{touched && error && (
<span id={id + "-error"} className="govuk-error-message">
<span className="govuk-visually-hidden">Error:</span>{" "}
{error}
</span>
)}
<select {...input} className="govuk-select " id={id} name={id}>
<option value="" disabled defaultValue>
{t("newappeal:select-default")}
</option>
{Object.keys(dropdownObj).map((key, index) => {
return (
<option key={key} value={dropdownObj[key].Value}>
{PickListTranslations(
dropdownObj[key].Label.LocalizedLabels[0]
.Label
)}
</option>
);
})}
</select>
</div>
</>
);
};
export function PickList(props) {
const { name, label, datafieldname, hint } = props;
let { t } = useTranslation();
const required = (value) => {
return value || value == 0
? undefined
: t("newappeal:is-required-label");
};
return (
<Field
name={props.name}
id={props.name}
label={props.label}
component={RenderPickList}
datafieldname={datafieldname}
picklistData={props.picklistData}
hint={props.hint}
errorMsg={t("newappeal:is-required-label")}
validate={[required]}
/>
);
}
export function NumericField(props) {
const {
name,
label,
validation,
form,
formProps,
parentFieldShowOnValue,
parentField,
maxFieldLength
} = props;
let { t } = useTranslation();
const required = (value) => {
return value || value == 0
? undefined
: t("newappeal:is-required-label");
};
const isNumber = (value) => {
const regex = /^\d+$/;
return regex.test(value)
? undefined
: t("newappeal:invalid-number-label");
};
const maxLength = (max) => (value) =>
value && value.length > max
? `Must be ${max} characters or less`
: undefined;
//parentFieldShowOnValue
var showIfHasParentShowValue =
parentField != false &&
!_.isEmpty(formProps[form]) &&
_.has(formProps[form].values, parentField) &&
parentFieldShowOnValue.indexOf(
formProps[form].values[parentField].toString()
) > -1;
(showIfHasParentShowValue == parentField) != false &&
showIfHasParentShowValue;
//console.log("parentField:", parentField, showIfHasParentShowValue);
const requiredMessage = t("newappeal:is-required-label");
const emojiNotAllowedMessage = t("newappeal:emojis-not-allowed-label");
const invalidPostcodeMessage = t("newappeal:invalid-postcode-label");
return (
<>
{parentField != false ? (
showIfHasParentShowValue && (
<div className="govuk-form-group">
<Field
name={props.name}
id={props.name}
type="number"
className="govuk-input govuk-input--width-20"
spellCheck="false"
aria-describedby={name}
pattern="[0-9]*"
inputMode="numeric"
parse={Number}
validate={(value) =>
validateField(
value,
validation,
requiredMessage,
emojiNotAllowedMessage,
invalidPostcodeMessage
)
} // Use the external validate function
component={RenderTextfield}
label={label}
maxFieldLength={props.maxFieldLength}
/>
</div>
)
) : (
<div className="govuk-form-group">
<Field
// name={props.name}
// id={props.name}
// type="text"
// className="govuk-input govuk-input--width-20 wwwww"
// aria-describedby={name}
// //pattern="^\d*\.?\d+$"
// pattern="[0-9]*"
// // inputMode="numeric"
// // parse={Number}
// validate={[
// required,
// // isNumber,
// maxLength(props.maxFieldLength),
// ]}
// component={RenderTextfield}
// label={label}
// // normalize={(value) => parseInt(value)}
// maxFieldLength={props.maxFieldLength}
name={props.name}
id={props.name}
type="text"
className="govuk-input govuk-input--width-10"
aria-describedby={name}
//pattern="^\d*\.?\d+$"
pattern="[0-9]*"
validate={[
required,
isNumber,
maxLength(props.maxFieldLength)
]}
component={RenderTextfield}
label={props.label}
maxFieldLength={props.maxFieldLength}
/>
</div>
)}
</>
);
}
export function DecimalField(props) {
const {
name,
label,
validation,
form,
formProps,
parentFieldShowOnValue,
parentField,
maxFieldLength
} = props;
let { t } = useTranslation();
const required = (value) =>
value ? undefined : t("newappeal:is-required-label");
const isNumber = (value) => {
const regex = /^\d*\.?\d{0,1}$/;
return regex.test(value)
? undefined
: t("newappeal:invalid-number-label");
};
const maxLength = (max) => (value) =>
value && value.length > max
? `Must be ${max} characters or less`
: undefined;
//parentFieldShowOnValue
var showIfHasParentShowValue =
parentField != false &&
!_.isEmpty(formProps[form]) &&
_.has(formProps[form].values, parentField) &&
parentFieldShowOnValue.indexOf(
formProps[form].values[parentField].toString()
) > -1;
(showIfHasParentShowValue == parentField) != false &&
showIfHasParentShowValue;
//console.log("parentField:", parentField, showIfHasParentShowValue);
const normalizeDecimal = (value) => {
return value ? parseFloat(value) : value;
};
const validateDecimal = (value) => {
if (!value) return t("newappeal:is-required-label");
// Regex to match whole numbers or decimal numbers (up to 7 characters including decimal)
const regex = /^\d{1,6}(\.\d{1,2})?$/; // Up to 6 digits before the decimal, 2 after
// Check if the value matches the regex pattern
if (!regex.test(value)) {
return t("newappeal:invalid-decimal-label");
}
return undefined;
};
const requiredMessage = t("newappeal:is-required-label");
const emojiNotAllowedMessage = t("newappeal:emojis-not-allowed-label");
const invalidPostcodeMessage = t("newappeal:invalid-postcode-label");
return (
<>
{parentField != false ? (
showIfHasParentShowValue && (
<div className="govuk-form-group">
<Field
name={props.name}
id={props.name}
// component="input"
type="number"
className="govuk-input govuk-input--width-10"
spellCheck="false"
aria-describedby={name}
pattern="[0-9]*"
inputMode="numeric"
parse={Number}
validate={(value) =>
validateField(
value,
validation,
requiredMessage,
emojiNotAllowedMessage,
invalidPostcodeMessage
)
} // Use the external validate function
component={RenderTextfield}
label={props.label}
maxFieldLength={props.maxFieldLength}
/>
</div>
)
) : (
<div className="govuk-form-group">
<Field
name={props.name}
id={props.name}
// component="input"
type="text"
className="govuk-input govuk-input--width-10"
spellCheck="false"
aria-describedby={name}
pattern="^\d*\.?\d+$"
// inputMode="numeric"
// parse={Number}
validate={[validateDecimal]}
component={RenderDecimalField}
label={props.label}
maxFieldLength={props.maxFieldLength}
// normalize={normalizeDecimal} // Normalizing input as float
/>
</div>
)}
</>
);
}
const RenderDecimalField = ({
name,
id,
label,
datafieldname,
className,
input,
errorMsg,
meta: { touched, error },
...custom
}) => {
// <div>
// <label>{label}</label>
// <input {...input} type="text" />
// {touched && error && <span>{error}</span>}
// </div>;
return (
<>
<div
className={
touched && error
? "govuk-form-group govuk-form-group--error"
: "govuk-form-group "
}
>
<label className="govuk-label " htmlFor={id} id={id + "_label"}>
{FieldsTranslations(label)}
</label>
{touched && error && (
<span id={id + "-error"} className="govuk-error-message">
<span className="govuk-visually-hidden">Error:</span>{" "}
{error}
</span>
)}
<input
{...input}
// type={custom.type}
className={className}
name={name}
id={id}
maxLength={
custom.hasOwnProperty("maxFieldLength")
? custom.maxFieldLength
? custom.maxFieldLength
: 100
: 200
}
/>
</div>
</>
);
};
const RenderCaseID = {};
export function ReadOnlyfield(props) {
const { name, label, value } = props;
//console.log(props.value.refno);
return (
<>
<div className="govuk-form-group">
<label className="govuk-label " htmlFor={props.name}>
{FieldsTranslations(props.label)}
</label>
<input
type="text"
name={props.name}
id={props.name}
//component={RenderCaseID}
value={value}
className="govuk-input govuk-input--width-20"
disabled
/>
</div>
</>
);
}
export const FieldsTranslations = (label) => {
const router = useRouter();
const { locale } = router;
const { appealtypes } = router.query;
let formObj = jsonpath({
path: "$['" + appealtypes + "']",
json: fieldLookup,
eval: true
});
let labelTrans =
router.locale == "cy"
? jsonpath({
path: '$..[?(@ && @.value=="' + label + '")].value_cy',
json: formObj,
eval: true
})[0]
: label;
//labelTrans = labelTrans.length > 1 ? labelTrans[0] : labelTrans;
return labelTrans;
};
const PickListTranslations = (optionValue) => {
const router = useRouter();
const { locale } = router;
const { appealtypes } = router.query;
//console.log(optionValue);
let optionTrans =
router.locale == "cy"
? jsonpath({
path: '$..[?(@ && @.value=="' + optionValue + '")].value_cy',
json: pickListLookup,
eval: true
})
: optionValue;
//console.log(optionTrans, optionValue);
return optionTrans;
};
export function FileUploadField(props) {
const uploadCount =
typeof props.uploadCount === "number" ? props.uploadCount : 0;
const setFileCount =
typeof props.setFileCount === "function"
? props.setFileCount
: () => {};
return (
<>
<h2 className="govuk-heading-s">
{FieldsTranslations(props.label)}{" "}
</h2>
<Field
name={props.name}
id={props.name}
component={RenderFileUpload}
className="govuk-input govuk-input--width-20"
//validate={[required]}
label={props.label}
errorMsg="Is required"
ticketnumber={props.ticketnumber}
documentTypeCode={props.documentTypeCode}
hint={props.hint}
fileList={props.fileList}
setFilesForAppeal={props.setFilesForAppeal}
containerID={props.containerID}
uploadCount={props.uploadCount}
setFileCount={props.setFileCount}
props={props.props}
/>
</>
);
}
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"
};
const RenderFileUpload = (field) => {
let files = field.input.value;
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) => (
<div key={file.name} className="govuk-!-margin-bottom-5 fileThumb">
<img src={file.preview} alt={file.name} width="50" /> -{" "}
<span className="govuk-body">
{file.name} ({file.size / 1024}kb)
</span>
</div>
));
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/png.png";
//return URL.createObjectURL(fileObj);
break;
default:
return "/assets/images/documenttypes/default.png";
break;
}
};
const getDocumentType = (docCode) => {
const repDate = new Date();
let day = repDate.getDate();
let month = repDate.getMonth() + 1;
let year = repDate.getFullYear();
const pdfFileNameDateStamp =
year + "-" + ("0" + month).slice(-2) + "-" + ("0" + day).slice(-2);
switch (docCode) {
case "000000":
return pdfFileNameDateStamp + "_-_Statement_of_Case";
case "000001":
return pdfFileNameDateStamp + "_-_Application_Form";
break;
case "000002":
return pdfFileNameDateStamp + "_-_Site_Ownership_Certificate";
break;
case "000003":
return pdfFileNameDateStamp + "_-_Decision_Notice";
break;
case "000004":
return pdfFileNameDateStamp + "_-_Site_Location_Plan";
break;
case "000005":
return pdfFileNameDateStamp + "_-_Plans_Drawing_Documents";
break;
case "000006":
return (
pdfFileNameDateStamp +
"_-_Additional_Plans_Drawings_Documents"
);
break;
case "000007":
return pdfFileNameDateStamp + "_-_Design_and_Access_Statement";
break;
case "000008":
return pdfFileNameDateStamp + "_-_NSB_LPA_Additional_Documents";
break;
case "000009":
return pdfFileNameDateStamp + "_-_LPA_Correspondence";
break;
case "000010":
return pdfFileNameDateStamp + "_-_LPA_Original_Permission";
break;
case "000011":
return pdfFileNameDateStamp + "_-_LPA's_Registration_Letter";
break;
case "000012":
return pdfFileNameDateStamp + "_-_Environmental_Statement";
break;
case "000013":
return pdfFileNameDateStamp + "_-_Cost_of_Application";
break;
case "000014":
return pdfFileNameDateStamp + "_-_Other_Relevant_Material";
break;
case "000015":
return (
pdfFileNameDateStamp +
"_-_S106_Agreement_or_Unilateral_Undertaking"
);
break;
default:
return pdfFileNameDateStamp + "_-_000000_-_";
}
};
const filelistObj = field.fileList || {};
const blobList = jsonpath({
path: "$..[?(@ && @.documentType=='" + field.documentTypeCode + "')]",
json: filelistObj,
eval: true
});
//const blobList = filelistObj;
const deleteThisBlob = async (
containerName,
blobName,
deleteblobhash,
getblobshash,
casefolderID
) => {
//console.log(containerName, blobName, deleteblobhash);
deleteBlob(containerName, blobName, deleteblobhash, casefolderID)
.then((data) => data)
.then(() => {
getFilesFromBlobHashed(
containerName,
getblobshash,
casefolderID
).then((newfilelist) => field.setFilesForAppeal(newfilelist));
});
};
const removeFile = (file) => {
const newFilesArr = files.filter((user) => user.name != file.name);
files == newFilesArr;
field.input.onChange(newFilesArr);
};
function removeEmptyObjects(arr) {
return arr.filter((obj) => Object.keys(obj).length > 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(
"newappeal:new-appeal-fileupload-file-error-filesize-label"
)}`;
}
if (error.code === "file-invalid-type") {
return `${file.name} ${t(
"newappeal:new-appeal-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(
"newappeal:new-appeal-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); // Clear any previous error messages when files are accepted
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(
"newappeal:new-appeal-fileupload-file-error-invalid-filename-label"
)}`
};
}
if (!FILENAME_ALLOWED.test(name)) {
return {
code: "filename-invalid-chars",
message: `${name} ${t(
"newappeal:new-appeal-fileupload-file-error-invalid-filename-label"
)}`
};
}
// block characters < > " / \ | ? *
if (/[<>"/\\|?*]/.test(name)) {
return {
code: "filename-invalid-chars",
message: `${name} ${t(
"newappeal:new-appeal-fileupload-file-error-invalid-filename-label"
)}`
};
}
return null; // valid
};
return (
<>
<Dropzone
key={field.input.name + "_key"}
maxSize={50 * 1024 * 1024}
accept={{
"application/pdf": [".pdf"],
"application/msword": [".doc"],
"application/vnd.openxmlformats-officedocument.wordprocessingml.document":
[".docx"],
"image/tiff": [".tif", ".tiff"],
"image/jpeg": [".jpg", ".jpeg"],
"image/png": [".png"],
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
[".xlsx"]
}}
validator={(file) => 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;
}
// continue with your existing upload logic for accepted files
setCompletedUploadFiles(false);
const renamedAcceptedFiles = acceptedFiles.map(
(file) =>
new File(
[file],
`${getDocumentType(field.documentTypeCode)}_-_${
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
)
.then((data) => {
let buildAppealFilesArray = [];
let values = field.form.appealForm.values;
let filesUploadObj = renamedAcceptedFiles;
for (let key in filesUploadObj) {
typeof filesUploadObj[key].name !==
"undefined" &&
buildAppealFilesArray.push({
name: filesUploadObj[key].name,
size: filesUploadObj[key].size
});
}
function removeDuplicates(arr, key) {
const seen = new Set();
return arr.filter((item) => {
const value = item[key];
if (seen.has(value)) return false;
seen.add(value);
return true;
});
}
buildAppealFilesArray = values.hasOwnProperty(
"filesList"
)
? buildAppealFilesArray.concat(values.filesList)
: buildAppealFilesArray;
buildAppealFilesArray = removeDuplicates(
buildAppealFilesArray,
"name"
);
Object.assign(values, {
filesList: buildAppealFilesArray
});
// keep your existing server-side invalid handling
if (data.invalidFiles?.length > 0) {
setRejectedFiles((prev) => [
...prev,
...data.invalidFiles
]);
setUploadCountMessage(
renamedAcceptedFiles.length -
data.invalidFiles.length
);
}
return getFilesFromBlobproxy(
field.containerID,
field.ticketnumber
);
})
.then((data) => {
field.setFilesForAppeal(data);
setCompletedUploadFiles(true);
});
}}
>
{({ getRootProps, getInputProps }) => (
<>
<section className="container">
<div {...getRootProps({ style })}>
{" "}
<input id={field.id} {...getInputProps()} />
<div className="govuk-!-font-size-14">
{t(
"newappeal:new-appeal-fileupload-drop-label"
)}{" "}
(Max 50 MB)
</div>
<em className="govuk-!-font-size-14">
(
{t(
"newappeal:new-appeal-fileupload-file-list-label"
)}
)
</em>
</div>
{/* <aside>{thumbs}</aside> */}
</section>
</>
)}
</Dropzone>
{rejectedFiles.length > 0 && (
<div
style={{ color: "red", marginTop: "10px" }}
className="govuk-body govuk-!-font-size-14 "
>
<ul>
{rejectedFiles.map((error, index) => (
<li key={index}>{error}</li>
))}
</ul>
</div>
)}
{uploadCountMessage > 0 && completedUploadFiles == false && (
<div className="govuk-body govuk-!-font-size-14">
{t("home:uploading-files-label", {
number: uploadCountMessage
})}
</div>
)}
{completedUploadFiles > 0 && uploadCountMessage >= 0 && (
<div className="govuk-body govuk-!-font-size-14">
{t("home:completed-uploading-files-label", {
number: uploadCountMessage
})}
</div>
)}
{field.meta.touched && field.meta.error && (
<span className="error">{field.meta.error}</span>
)}
{files && Array.isArray(files) && (
<>
{/* {(files = removeEmptyObjects(files))} */}
{!completedUploadFiles &&
files.map(
(file, i) =>
typeof file.name != "undefined" && (
<div
key={file.name}
className="govuk-!-margin-bottom-5 fileThumb"
>
{" "}
{/* <span
className="govuk-summary-list__actionLink watched_link govuk-!-margin-right-5 govuk-!-text-align-left"
onClick={() => removeFile(file)}
>
&minus;
</span> */}
<img
src={getThumbnailIcon(
file,
file.type
)}
alt={file.name}
width="50"
/>
<span className="govuk-body govuk-!-font-size-14 govuk-!-padding-left-5">
{file.name} (
{bytesToSize(file.size)}) <br />
<div id={file.name + "_" + i}>
{completedUploadFiles ? (
<p className="govuk-body govuk-!-font-size-14">
Upload complete
</p>
) : (
<p className="govuk-body textloading govuk-!-font-size-14">
{t(
"home:uploading-files-progress-label"
)}{" "}
</p>
)}
</div>
</span>
</div>
)
)}
</>
)}
{blobList.length > 0 && (
<h4>{t("newappeal:previously-added-files")}</h4>
)}
{blobList.map(
(blob, i) =>
blob.documentType == field.documentTypeCode && (
<div
key={blob.name + "_" + i}
className="govuk-!-margin-bottom-5 fileThumb govuk-!-margin-top-5"
>
<div key={"deleteLnk" + i} className="">
<span
className="govuk-summary-list__actionLink watched_link govuk-!-margin-right-5 govuk-!-text-align-left"
onClick={() => {
setUploadCountMessage(
uploadCountMessage - 1
);
deleteThisBlob(
field.containerID,
blob.name,
blob.hasheddeletepath,
blob.hashgetblobs,
field.ticketnumber
);
}}
title={t("home:remove-this-file-label")}
>
&minus;
</span>
</div>
<img
src={getThumbnailIconByExtension(blob.name)}
alt={blob.name}
width="50"
/>
<Link
key={"lnk" + i}
scroll={false}
href={
"/api/file/downloadblob?container=" +
field.containerID +
"&casefolderID=" +
encodeURIComponent(field.ticketnumber) +
"&blobname=" +
encodeURIComponent(blob.name) +
blob.hashedfilepath
}
className="govuk-body govuk-!-font-size-14 govuk-!-padding-left-5 govuk-link"
>
{blob.name} ({bytesToSize(blob.contentLength)})
</Link>
</div>
)
)}{" "}
</>
);
};
const renderField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<label>{label}</label>
<div>
<input {...input} type={type} placeholder={label} />
{touched && error && <span>{error}</span>}
</div>
</div>
);
const RenderSubFields = ({ fields, meta: { touched, error }, ...custom }) => {
let { t } = useTranslation();
let subTitle = fields.name.split("pinswg_")[1];
const checkValue = (value) => {
let errors;
if (!value) {
errors = t("newappeal:is-required-label");
} else {
// Check for emojis using a regular expression
const emojiRegex =
/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F700}-\u{1F77F}\u{1F780}-\u{1F7FF}\u{1F800}-\u{1F8FF}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{2300}-\u{23FF}\u{2B50}\u{1F004}-\u{1F0CF}\u{1F0A0}-\u{1F0A5}\u{1F170}-\u{1F251}]/gu;
if (emojiRegex.test(value)) {
errors = "Emojis are not allowed";
}
}
return errors;
};
const required = (value) =>
value ? undefined : t("newappeal:is-required-label");
const email = (value) => {
let errors;
const emailRegex =
/(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/gi;
if (!emailRegex.test(value)) {
errors = t("newappeal:invalid-email-address-errormsg");
}
return errors;
};
//fields.push({});
fields.length == 0 && fields.push({});
return (
<ul className="subFieldList">
{fields.map((member, index) => (
<div key={index}>
<h4>
{subTitle == "owners" ? "Owner" : "Tenant"} #{index + 1}
</h4>
<div className="fieldArrayContainer">
<div className="govuk-grid-column-full">
<div
className="gouk-grid-row"
style={{
display: "flex",
marginBottom: "20px"
}}
>
<Field
id={`${member}.${fields.name}_firstname`}
name={`${member}.${fields.name}_firstname`}
type="text"
component={RenderTextfield}
validate={[checkValue]}
label={"First name"}
className="govuk-input govuk-input--width-12"
showErrorBottom={true}
/>
<Field
id={`${member}.${fields.name}_lastname`}
name={`${member}.${fields.name}_lastname`}
type="text"
component={RenderTextfield}
validate={[checkValue]}
label={"Last name"}
className="govuk-input govuk-input--width-12"
showErrorBottom={true}
/>
</div>
<div
className="gouk-grid-row"
style={{ display: "flex" }}
>
<Field
id={`${member}.${fields.name}_email`}
name={`${member}.${fields.name}_email`}
type="text"
component={RenderTextfield}
validate={[email]}
label={"Email"}
className="govuk-input govuk-input--width-12"
showErrorBottom={true}
/>
<Field
name={`${member}.dateserved`}
type="text"
component={RenderDatePicker}
label="Date served "
dateStart={"-6m"}
dateEnd={"0"}
validate={[required]}
className="govuk-input govuk-input--width-12"
showErrorBottom={true}
/>
</div>
</div>
<div
className="gouk-grid-row"
style={{ display: "flex" }}
>
<button
type="button"
title="Remove"
onClick={() => fields.remove(index)}
className="govuk-button fieldArrayDelete"
>
X
</button>
</div>
</div>
</div>
))}
<br />
{fields.length < custom.maxSubField && (
<button
type="button"
className="govuk-link fieldArrayLink"
onClick={() => fields.push({})}
>
{subTitle == "owners"
? t("newappeal:new-appeal-add-owner-label")
: t("newappeal:new-appeal-add-tenant-label")}
</button>
)}
</ul>
);
};
export const FieldArrayForm = (props) => {
const {
name,
label,
validation,
form,
formProps,
parentFieldShowOnValue,
parentField,
maxFieldLength,
maxSubField
} = props;
const dispatch = useDispatch(); // Get dispatch using useDispatch hook
var showIfHasParentShowValue =
parentField != false &&
!_.isEmpty(formProps[form]) &&
_.has(formProps[form].values, parentField) &&
parentFieldShowOnValue.indexOf(
formProps[form].values[parentField].toString()
) > -1;
(showIfHasParentShowValue == parentField) != false &&
showIfHasParentShowValue;
!showIfHasParentShowValue && dispatch(change("appealForm", name, null));
parentFieldShowOnValue;
const { handleSubmit, pristine, reset, submitting } = props;
return (
<>
{" "}
{showIfHasParentShowValue && (
<FieldArray
name={name}
component={RenderSubFields}
maxSubField={maxSubField}
/>
)}
</>
);
};