mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-09-04 16:58:05 +02:00
867 lines
30 KiB
React
867 lines
30 KiB
React
import React, { forwardRef, useEffect, useMemo, useRef, useState } from "react";
|
|
import ModalUi from "../../primitives/ModalUi";
|
|
import {
|
|
getMonth,
|
|
getYear,
|
|
radioButtonWidget,
|
|
compressedFileSize,
|
|
textWidget,
|
|
months,
|
|
changeDateToMomentFormat,
|
|
convertBase64ToFile,
|
|
generatePdfName,
|
|
drawWidget,
|
|
getBase64MimeType,
|
|
isBase64
|
|
} from "../../constant/Utils";
|
|
import DatePicker from "react-datepicker";
|
|
import "react-datepicker/dist/react-datepicker.css";
|
|
import { range } from "pdf-lib";
|
|
import { useTranslation } from "react-i18next";
|
|
import moment from "moment";
|
|
import AsyncSelect from "react-select/async";
|
|
import axios from "axios";
|
|
import AddContact from "../../primitives/AddContact";
|
|
import Loader from "../../primitives/Loader";
|
|
import { useDispatch, useSelector } from "react-redux";
|
|
import {
|
|
resetWidgetState,
|
|
setPrefillImg
|
|
} from "../../redux/reducers/widgetSlice";
|
|
import * as utils from "../../utils";
|
|
import Draw from "./tab/Draw";
|
|
import PenColorComponent from "./tab/PenColorComponent";
|
|
|
|
const widgetTitle = "font-medium";
|
|
|
|
const ShowTextWidget = ({ position, handleWidgetDetails }) => {
|
|
const inputRef = useRef(null);
|
|
const [inputValue, setInputValue] = useState(position.options.response || "");
|
|
|
|
return (
|
|
<input
|
|
ref={inputRef}
|
|
rows={1}
|
|
value={inputValue}
|
|
onChange={(e) => {
|
|
setInputValue(e.target.value);
|
|
handleWidgetDetails(position, e.target.value);
|
|
}}
|
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
|
/>
|
|
);
|
|
};
|
|
|
|
const ImageComponent = (props) => {
|
|
const { t } = useTranslation();
|
|
const prefillImg = useSelector((state) => state.widget.prefillImg);
|
|
const imageRefs = useRef({});
|
|
|
|
let imgUrl = "";
|
|
const isBase64Url = isBase64(props?.position?.SignUrl);
|
|
if (isBase64Url) {
|
|
imgUrl = props?.position?.SignUrl;
|
|
} else {
|
|
const getPrefillImg = prefillImg?.find(
|
|
(x) => x.id === props?.position?.key
|
|
);
|
|
imgUrl = getPrefillImg?.base64;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<span className={widgetTitle}>{props?.position.options?.name}</span>
|
|
{imgUrl ? (
|
|
<>
|
|
<div className="cursor-pointer op-card border-[1px] border-gray-400 flex flex-col w-full h-full justify-center items-center ">
|
|
<img
|
|
alt="print img"
|
|
ref={(el) => (imageRefs.current[props?.id] = el)} // Assign ref dynamicallys
|
|
src={imgUrl}
|
|
draggable="false"
|
|
className="object-contain h-full w-full aspect-[5/2]"
|
|
onLoad={() => props?.handleImageLoaded?.(props?.position.key)}
|
|
onError={() => props?.handleImageLoaded?.(props?.position.key)}
|
|
/>
|
|
</div>
|
|
<span
|
|
onClick={() => props?.handleClearImage(props?.position)}
|
|
className="flex justify-start text-blue-500 underline cursor-pointer"
|
|
>
|
|
{t("clear")}
|
|
</span>
|
|
</>
|
|
) : (
|
|
<div
|
|
className="cursor-pointer op-card border-[1px] op-border-hover flex flex-col overflow-hidden w-full h-full aspect-[5/2] justify-center items-center"
|
|
onClick={() => imageRefs.current[props?.id]?.click()}
|
|
>
|
|
<input
|
|
type="file"
|
|
onChange={(e) => props?.onImageChange?.(e, props?.position)}
|
|
className="filetype"
|
|
accept="image/png,image/jpeg"
|
|
ref={(el) => (imageRefs.current[props?.id] = el)} // Assign ref dynamically
|
|
hidden
|
|
/>
|
|
<i className="fa-light text-base-content fa-cloud-upload-alt text-[25px]"></i>
|
|
<div className="text-[10px] text-base-content">{t("upload")}</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
function PrefillWidgetModal(props) {
|
|
const { t } = useTranslation();
|
|
const dispatch = useDispatch();
|
|
const canvasRefs = useRef({});
|
|
const [penColor, setPenColor] = useState("blue");
|
|
// Track already loaded image keys so they don't increment multiple times
|
|
const loadedSet = useRef(new Set());
|
|
const initializedRef = useRef(false); // prevent rerun on state updates
|
|
const [image, setImage] = useState(null);
|
|
const [currentWidget, setCurrentWidget] = useState("");
|
|
const [userList, setUserList] = useState([]);
|
|
const [totalImages, setTotalImages] = useState(0);
|
|
const [loadedImages, setLoadedImages] = useState(0);
|
|
const [loading, setLoading] = useState(false);
|
|
const years = range(1950, getYear(new Date()) + 16, 1);
|
|
|
|
// useMemo to memoize the calculation of unique widgets
|
|
const uniqueWidget = useMemo(() => {
|
|
//functions to used remove duplicate name values across all pages
|
|
if (!props.prefillData) return [];
|
|
//This will help us track which name values have already been encountered across all pages.
|
|
const uniqueNames = new Set();
|
|
//Filter and flatten placeholder widgets while keeping unique names
|
|
const filteredArray = props.prefillData?.placeHolder?.map((item) => ({
|
|
...item,
|
|
pos: item.pos.filter((curr) => {
|
|
if (uniqueNames.has(curr?.options?.name)) return false; //Duplicate name found, remove it
|
|
uniqueNames.add(curr?.options?.name); //First time seen, add to set
|
|
return true;
|
|
})
|
|
}));
|
|
//latten the filtered array and exclude read-only widgets
|
|
const flatArray = filteredArray?.flatMap((page) =>
|
|
page.pos
|
|
.filter((widget) => !widget.options?.isReadOnly)
|
|
.map((widget) => ({
|
|
widget,
|
|
pageNumber: page.pageNumber
|
|
}))
|
|
);
|
|
|
|
return flatArray || [];
|
|
}, [props.prefillData]);
|
|
|
|
useEffect(() => {
|
|
dispatch(resetWidgetState([]));
|
|
}, []);
|
|
// Reset loader state when modal closes
|
|
useEffect(() => {
|
|
if (!props?.isPrefillModal) {
|
|
initializedRef.current = false;
|
|
setTotalImages(0);
|
|
setLoadedImages(0);
|
|
setLoading(false);
|
|
}
|
|
}, [props?.isPrefillModal]);
|
|
|
|
useEffect(() => {
|
|
setLoading(true);
|
|
//function is used to save all image base64 in redux state to display prefill images
|
|
const savePrefillImg = async () => {
|
|
const prefillImg = await utils?.savePrefillImg(props.xyPosition);
|
|
if (Array.isArray(prefillImg)) {
|
|
prefillImg.forEach((img) => dispatch(setPrefillImg(img)));
|
|
}
|
|
setLoading(false);
|
|
};
|
|
savePrefillImg();
|
|
}, [props.xyPosition]);
|
|
|
|
useEffect(() => {
|
|
if (totalImages > 0 && loadedImages === totalImages) {
|
|
setLoading(false);
|
|
}
|
|
}, [loadedImages, totalImages]);
|
|
useEffect(() => {
|
|
if (image?.src) {
|
|
handleWidgetDetails(currentWidget);
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [image]);
|
|
|
|
// Run only once per modal open
|
|
useEffect(() => {
|
|
if (props?.isPrefillModal && !initializedRef.current) {
|
|
const getImgWidgets = uniqueWidget?.filter(
|
|
(w) => w.widget?.type === "image" && w.widget?.options?.response
|
|
);
|
|
if (getImgWidgets?.length > 0) {
|
|
const imgCount = getImgWidgets.length;
|
|
setTotalImages(imgCount);
|
|
setLoadedImages(0);
|
|
setLoading(true);
|
|
initializedRef.current = true; // mark as initialized
|
|
}
|
|
}
|
|
}, [props?.isPrefillModal, uniqueWidget]);
|
|
|
|
// The getDatePickerDate function retrieves the date in the correct format supported by the DatePicker.
|
|
const getDatePickerDate = (selectedDate, format = "dd-MM-yyyy") => {
|
|
let date;
|
|
if (format && format === "dd-MM-yyyy") {
|
|
const [day, month, year] = selectedDate?.split("-");
|
|
date = new Date(`${year}-${month}-${day}`);
|
|
} else if (format && format === "dd.MM.yyyy") {
|
|
const [day, month, year] = selectedDate?.split(".");
|
|
date = new Date(`${year}.${month}.${day}`);
|
|
} else if (format && format === "dd/MM/yyyy") {
|
|
const [day, month, year] = selectedDate?.split("/");
|
|
date = new Date(`${year}/${month}/${day}`);
|
|
} else {
|
|
date = new Date(selectedDate);
|
|
}
|
|
return date;
|
|
};
|
|
|
|
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
|
|
<div
|
|
style={{ fontFamily: "Arial, sans-serif" }}
|
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full"
|
|
onClick={onClick}
|
|
ref={ref}
|
|
>
|
|
{value}
|
|
<i className="fa-light fa-calendar ml-[5px]"></i>
|
|
</div>
|
|
));
|
|
ExampleCustomInput.displayName = "ExampleCustomInput";
|
|
|
|
const handleDate = (position) => {
|
|
// The getDatePickerDate function retrieves the date in the correct format supported by the DatePicker.
|
|
const getDate = getDatePickerDate(
|
|
position?.options.response || new Date(),
|
|
position?.options?.validation?.format
|
|
);
|
|
return getDate;
|
|
};
|
|
//function to set date with required date format onchange date
|
|
const handleOnDateChange = (date, position) => {
|
|
const format = position?.options?.validation?.format || "MM/dd/yyyy";
|
|
let updateDate = date;
|
|
let newDate;
|
|
const isSpecialDateFormat =
|
|
format && ["dd-MM-yyyy", "dd.MM.yyyy", "dd/MM/yyyy"].includes(format);
|
|
if (isSpecialDateFormat) {
|
|
newDate = moment(updateDate).format(changeDateToMomentFormat(format));
|
|
} else {
|
|
//using moment package is used to change date as per the format provided in selectDate obj e.g. - MM/dd/yyyy -> 03/12/2024
|
|
newDate = new Date(updateDate);
|
|
newDate = moment(newDate.getTime()).format(
|
|
changeDateToMomentFormat(format)
|
|
);
|
|
}
|
|
handleWidgetDetails(position, newDate);
|
|
};
|
|
|
|
const handleSavePrefillImg = async (base64) => {
|
|
setLoading(true);
|
|
try {
|
|
const imageName = generatePdfName(16);
|
|
const imgType = image?.imgType || getBase64MimeType(base64);
|
|
const imageUrl = await convertBase64ToFile(
|
|
imageName,
|
|
base64 || image.src,
|
|
imgType
|
|
);
|
|
if (imageUrl) {
|
|
return imageUrl;
|
|
}
|
|
} catch (e) {
|
|
console.log("error in handleSavePrefillImg function ", e);
|
|
}
|
|
};
|
|
//function is used to handle prefill widgets details and check if there are any duplicate widget name field exist then update all duplicate value
|
|
const handleWidgetDetails = async (widgetDetails, response) => {
|
|
const widgetName = widgetDetails?.options?.name;
|
|
const getPrefill = props.xyPosition.find((x) => x?.Role === "prefill");
|
|
const getPlaceholder = getPrefill?.placeHolder;
|
|
let imgUrl;
|
|
if (widgetDetails?.type === "image") {
|
|
imgUrl = await handleSavePrefillImg(response);
|
|
}
|
|
const updatedData = getPlaceholder.map((page) => ({
|
|
...page,
|
|
pos: page.pos.map((item) => {
|
|
if (item.options.name === widgetName) {
|
|
if (widgetDetails?.type === "image") {
|
|
return {
|
|
...item,
|
|
SignUrl: imgUrl,
|
|
ImageType: image.imgType,
|
|
options: { ...item.options, response: imgUrl }
|
|
};
|
|
} else {
|
|
return {
|
|
...item,
|
|
options: { ...item.options, response: response }
|
|
};
|
|
}
|
|
} else {
|
|
return item;
|
|
}
|
|
})
|
|
}));
|
|
const newUpdateSigner = props.xyPosition.map((obj) => {
|
|
if (obj.Role === "prefill") {
|
|
return { ...obj, placeHolder: updatedData };
|
|
}
|
|
return obj;
|
|
});
|
|
props.setXyPosition(newUpdateSigner);
|
|
};
|
|
|
|
//function for set checked and unchecked value of checkbox
|
|
const handleCheckboxValue = (isChecked, ind, position) => {
|
|
let updateSelectedCheckbox = [];
|
|
updateSelectedCheckbox =
|
|
position.options?.defaultValue || position.options?.response || [];
|
|
if (isChecked) {
|
|
updateSelectedCheckbox.push(ind);
|
|
} else {
|
|
updateSelectedCheckbox = updateSelectedCheckbox.filter(
|
|
(data) => data !== ind
|
|
);
|
|
}
|
|
handleWidgetDetails(position, updateSelectedCheckbox);
|
|
};
|
|
|
|
//function for image upload or update
|
|
const onImageChange = (event, position) => {
|
|
if (event.target.files && event.target.files[0]) {
|
|
const file = event.target.files[0];
|
|
compressedFileSize(file, setImage);
|
|
setCurrentWidget(position);
|
|
}
|
|
};
|
|
const handleRadioCheck = (data, position) => {
|
|
const res = position?.options?.response;
|
|
const defaultCheck = position?.options?.defaultValue;
|
|
if (res === data || defaultCheck === data) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
//function for show checked checkbox
|
|
const selectCheckbox = (ind, position) => {
|
|
const res = position?.options?.response;
|
|
const defaultCheck = position?.options?.defaultValue;
|
|
if (res && res?.length > 0) {
|
|
const isSelectIndex = res.indexOf(ind);
|
|
if (isSelectIndex > -1) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else if (defaultCheck) {
|
|
const isSelectIndex = defaultCheck.indexOf(ind);
|
|
if (isSelectIndex > -1) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
const handleClearImage = (position) => {
|
|
let prefillPlaceholder = props?.xyPosition.filter(
|
|
(data) => data?.Role === "prefill"
|
|
);
|
|
const updatedArray = prefillPlaceholder[0]?.placeHolder?.map((page) => ({
|
|
...page,
|
|
pos: page.pos.map((item) => {
|
|
if (item.options.name === position.options.name) {
|
|
return {
|
|
...item,
|
|
...(item.SignUrl !== undefined && { SignUrl: "" }),
|
|
options: {
|
|
...position.options,
|
|
response: ""
|
|
}
|
|
};
|
|
}
|
|
return item;
|
|
})
|
|
}));
|
|
const newUpdateSigner = props.xyPosition.map((obj) => {
|
|
if (obj.Role === "prefill") {
|
|
return { ...obj, placeHolder: updatedArray };
|
|
}
|
|
return obj;
|
|
});
|
|
props.setXyPosition(newUpdateSigner);
|
|
};
|
|
const handleImageLoaded = (key) => {
|
|
// Prevent counting the same image multiple times.
|
|
// If this image (key) has not already been marked as loaded...
|
|
if (!loadedSet.current.has(key)) {
|
|
// Mark this image as loaded by adding its key to the Set
|
|
loadedSet.current.add(key);
|
|
// Increment the loadedImages state by 1
|
|
// (tracks how many images have finished loading)
|
|
setLoadedImages((prev) => prev + 1);
|
|
}
|
|
setLoading(false);
|
|
};
|
|
//function for set signature url
|
|
const handleSignatureChange = (drawImage, position) => {
|
|
handleWidgetDetails(position, drawImage);
|
|
};
|
|
const getCanvasRef = (widgetId) => {
|
|
if (!canvasRefs.current[widgetId]) {
|
|
canvasRefs.current[widgetId] = React.createRef();
|
|
}
|
|
return canvasRefs.current[widgetId];
|
|
};
|
|
const clearCanvasById = (widgetId) => {
|
|
const canvasRef = canvasRefs.current?.[widgetId];
|
|
|
|
if (canvasRef?.current) {
|
|
canvasRef.current.clear();
|
|
}
|
|
};
|
|
const handleWidgetType = (position, id) => {
|
|
switch (position?.type) {
|
|
case "checkbox":
|
|
return (
|
|
<>
|
|
<span className={widgetTitle}>{position.options?.name}</span>
|
|
<div className="flex flex-col gap-y-1">
|
|
{position.options?.values?.map((data, ind) => (
|
|
<div
|
|
key={ind}
|
|
className="select-none-cls flex items-center text-center gap-0.5"
|
|
>
|
|
<input
|
|
id={`checkbox-${position.key + ind}`}
|
|
className="mt-[2px] op-checkbox op-checkbox-xs"
|
|
type="checkbox"
|
|
checked={selectCheckbox(ind, position)}
|
|
onChange={(e) =>
|
|
handleCheckboxValue(e.target.checked, ind, position)
|
|
}
|
|
/>
|
|
<label
|
|
htmlFor={`checkbox-${position.key + ind}`}
|
|
className="text-xs mb-0 text-center ml-[3px] cursor-pointer"
|
|
>
|
|
{data}
|
|
</label>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</>
|
|
);
|
|
case textWidget:
|
|
return (
|
|
<>
|
|
<span className={widgetTitle}>{position.options?.name}</span>
|
|
<ShowTextWidget
|
|
position={position}
|
|
handleWidgetDetails={handleWidgetDetails}
|
|
/>
|
|
</>
|
|
);
|
|
case "dropdown":
|
|
return (
|
|
<>
|
|
<span className={widgetTitle}>{position.options?.name}</span>
|
|
<select
|
|
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-base-content w-full"
|
|
id="myDropdown"
|
|
value={
|
|
position?.options?.response || position?.options?.defaultValue
|
|
}
|
|
onChange={(e) => handleWidgetDetails(position, e.target.value)}
|
|
>
|
|
{/* Default/Title option */}
|
|
<option value="" disabled hidden>
|
|
{t("choose-one")}
|
|
</option>
|
|
{position?.options?.values?.map((data, ind) => (
|
|
<option key={ind} value={data}>
|
|
{data}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</>
|
|
);
|
|
case "date":
|
|
return (
|
|
<>
|
|
<span className={widgetTitle}>{position.options?.name}</span>
|
|
<DatePicker
|
|
portalId="datepicker-portal-root"
|
|
renderCustomHeader={({ date, changeYear, changeMonth }) => (
|
|
<div className="flex justify-start ml-2">
|
|
<select
|
|
className="bg-transparent outline-none"
|
|
value={months[getMonth(date)]}
|
|
onChange={({ target: { value } }) =>
|
|
changeMonth(months.indexOf(value))
|
|
}
|
|
>
|
|
{months.map((option) => (
|
|
<option key={option} value={option}>
|
|
{option}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<select
|
|
className="bg-transparent outline-none"
|
|
value={getYear(date)}
|
|
onChange={({ target: { value } }) => changeYear(value)}
|
|
>
|
|
{years.map((option) => (
|
|
<option key={option} value={option}>
|
|
{option}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
)}
|
|
closeOnScroll={true}
|
|
selected={handleDate(position)}
|
|
onChange={(date) => handleOnDateChange(date, position)}
|
|
customInput={<ExampleCustomInput />}
|
|
dateFormat={position?.options?.validation?.format || "MM/dd/yyyy"}
|
|
/>
|
|
</>
|
|
);
|
|
case "image":
|
|
return (
|
|
<ImageComponent
|
|
position={position}
|
|
onImageChange={onImageChange}
|
|
handleImageLoaded={handleImageLoaded}
|
|
handleClearImage={handleClearImage}
|
|
id={id}
|
|
/>
|
|
);
|
|
case radioButtonWidget:
|
|
return (
|
|
<>
|
|
<span className={widgetTitle}>{position.options?.name}</span>
|
|
<div className="flex flex-col gap-y-1">
|
|
{position.options?.values.map((data, ind) => (
|
|
<div
|
|
key={ind}
|
|
className="select-none-cls flex items-center text-center gap-0.5"
|
|
>
|
|
<input
|
|
id={`radio-${position.key + ind}`}
|
|
className="mt-[2px] op-radio op-radio-xs"
|
|
type="radio"
|
|
checked={handleRadioCheck(data, position)}
|
|
onChange={() => handleWidgetDetails(position, data)}
|
|
/>
|
|
<label
|
|
htmlFor={`radio-${position.key + ind}`}
|
|
className="text-xs mb-0 ml-[2px] cursor-pointer"
|
|
>
|
|
{data}
|
|
</label>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</>
|
|
);
|
|
case drawWidget:
|
|
return (
|
|
<div>
|
|
<span className={widgetTitle}>{position.options?.name}</span>
|
|
<Draw
|
|
penColor={penColor}
|
|
canvasRef={getCanvasRef(position.key)}
|
|
currWidgetsDetails={position}
|
|
handleSignatureChange={handleSignatureChange}
|
|
prefillCls={"prefillCanvas"}
|
|
/>
|
|
<div className="flex flex-row justify-between mt-[10px]">
|
|
<PenColorComponent
|
|
penColor={penColor}
|
|
setPenColor={setPenColor}
|
|
/>
|
|
<span
|
|
onClick={() => {
|
|
clearCanvasById(position?.key);
|
|
handleClearImage(position);
|
|
}}
|
|
className="flex justify-start text-blue-500 underline cursor-pointer"
|
|
>
|
|
{t("clear")}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
default:
|
|
return position?.SignUrl ? (
|
|
<div className="pointer-events-none">
|
|
<img
|
|
alt="image"
|
|
draggable="false"
|
|
src={position?.SignUrl}
|
|
className="w-full h-full"
|
|
/>
|
|
</div>
|
|
) : (
|
|
<div className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full">
|
|
No widget
|
|
</div>
|
|
);
|
|
}
|
|
};
|
|
|
|
const handleEmbedPrefill = async (item) => {
|
|
//checking is there any draw widget response have base64 url then generate that base64 to url and save it
|
|
const prefillWidgets = props.xyPosition?.find((x) => x.Role === "prefill");
|
|
let updatedXyPosition = [];
|
|
if (prefillWidgets) {
|
|
const isDrawWidget = (prefillWidgets.placeHolder ?? []).some((ph) =>
|
|
(ph?.pos ?? []).some((p) => p?.type === drawWidget)
|
|
);
|
|
if (isDrawWidget) {
|
|
const getPrefill = props.xyPosition.find((x) => x?.Role === "prefill");
|
|
const getPlaceholder = getPrefill?.placeHolder ?? [];
|
|
const updatedData = await Promise.all(
|
|
getPlaceholder.map(async (page) => ({
|
|
...page,
|
|
pos: await Promise.all(
|
|
page.pos.map(async (item) => {
|
|
if (item?.type === drawWidget) {
|
|
const widgetResponse = item?.options?.response;
|
|
|
|
// Skip if no response
|
|
if (!widgetResponse) return item;
|
|
|
|
// If already URL, do not re-upload
|
|
if (
|
|
typeof widgetResponse === "string" &&
|
|
widgetResponse.startsWith("http")
|
|
) {
|
|
return item;
|
|
}
|
|
|
|
// Convert THIS widget's base64 → URL
|
|
const drawUrl = await handleSavePrefillImg(widgetResponse);
|
|
return {
|
|
...item,
|
|
options: {
|
|
...item.options,
|
|
response: drawUrl
|
|
}
|
|
};
|
|
}
|
|
|
|
return item;
|
|
})
|
|
)
|
|
}))
|
|
);
|
|
|
|
updatedXyPosition = props.xyPosition.map((obj) => {
|
|
if (obj.Role === "prefill") {
|
|
return {
|
|
...obj,
|
|
placeHolder: updatedData
|
|
};
|
|
}
|
|
return obj;
|
|
});
|
|
|
|
props.setXyPosition(updatedXyPosition);
|
|
}
|
|
}
|
|
|
|
await props.handleCreateDocument();
|
|
};
|
|
//`loadOptions` function to use show all list of signer in dropdown
|
|
const loadOptions = async (inputValue) => {
|
|
try {
|
|
const baseURL = localStorage.getItem("baseUrl");
|
|
const url = `${baseURL}functions/getsigners`;
|
|
|
|
const token =
|
|
{ "X-Parse-Session-Token": localStorage.getItem("accesstoken") };
|
|
const headers = {
|
|
"Content-Type": "application/json",
|
|
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
|
...token
|
|
};
|
|
const search = inputValue;
|
|
const axiosRes = await axios.post(url, { search }, { headers });
|
|
const contactRes = axiosRes?.data?.result || [];
|
|
if (contactRes) {
|
|
const res = JSON.parse(JSON.stringify(contactRes));
|
|
const result = res;
|
|
setUserList(result);
|
|
return await result.map((item) => ({
|
|
label: `${item.Name}<${item.Email}>`,
|
|
value: item.objectId
|
|
}));
|
|
}
|
|
} catch (error) {
|
|
console.log("err", error);
|
|
}
|
|
};
|
|
//`handleInputChange` function to get signers list from dropdown
|
|
const handleInputChange = (item, id) => {
|
|
const signerExist = props.forms.some((x) => x.label === item.label);
|
|
if (signerExist) {
|
|
alert(t("already-exist-signer"));
|
|
} else {
|
|
let newForm = [...props.forms];
|
|
let signerId = newForm[id].value;
|
|
newForm[id].label = item?.label;
|
|
// newForm[id].value = item?.value;
|
|
props.setForms(newForm);
|
|
const getSigner = userList.find((x) => x.objectId === item.value);
|
|
props.handleAddUser(getSigner, signerId);
|
|
}
|
|
};
|
|
//show modal to create new contact
|
|
const handleCreateNew = (e, id) => {
|
|
e.preventDefault();
|
|
props.setIsNewContact({ status: true, id: id });
|
|
};
|
|
const closePopup = () => {
|
|
props.setIsNewContact({ status: false, id: "" });
|
|
};
|
|
return (
|
|
<>
|
|
<ModalUi
|
|
title={uniqueWidget?.length > 0 ? t("prefill-widget") : "Recipients"}
|
|
isOpen={true}
|
|
handleClose={props.handleClosePrefillModal}
|
|
>
|
|
<div className="relative">
|
|
{(props?.isSubmit || loading) && (
|
|
<div className="absolute inset-0 flex items-center justify-center bg-white/70 z-[9999]">
|
|
<Loader />
|
|
</div>
|
|
)}
|
|
{uniqueWidget?.length > 0 && (
|
|
<div className="py-3 px-[10px] op-card border-[1px] border-gray-400 m-3 md:m-6 text-base-content flex flex-col relative">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-10 gap-y-4 w-full">
|
|
{uniqueWidget.map((x, id) => (
|
|
<div key={id} className="flex flex-col gap-2 w-full">
|
|
{handleWidgetType(x.widget, id)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div>
|
|
{props.forms.length > 0 && (
|
|
<div className="overflow-y-auto m-3">
|
|
{uniqueWidget?.length > 0 && (
|
|
<h1 className="font-medium text-[15px] mb-2">
|
|
{t("recipients")}
|
|
</h1>
|
|
)}
|
|
<div className="py-3 px-[10px] op-card border-[1px] border-gray-400 md:mx-3 text-base-content flex flex-col relative">
|
|
{props.forms?.map((field, id) => {
|
|
return (
|
|
<div className="flex flex-col" key={field?.value}>
|
|
<label>{field?.role}</label>
|
|
<div className="flex justify-between items-center gap-1">
|
|
<div className="flex-1">
|
|
<AsyncSelect
|
|
cacheOptions
|
|
defaultOptions
|
|
value={field}
|
|
loadingMessage={() => t("loading")}
|
|
noOptionsMessage={() => t("contact-not-found")}
|
|
loadOptions={loadOptions}
|
|
onChange={(item) => handleInputChange(item, id)}
|
|
unstyled
|
|
onFocus={() => loadOptions()}
|
|
classNames={{
|
|
control: () =>
|
|
"op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full h-full text-[11px]",
|
|
valueContainer: () =>
|
|
"flex flex-row gap-x-[2px] gap-y-[2px] md:gap-y-0 w-full my-[2px]",
|
|
multiValue: () =>
|
|
"op-badge op-badge-primary h-full text-[11px]",
|
|
multiValueLabel: () => "mb-[2px]",
|
|
menu: () =>
|
|
"mt-1 shadow-md rounded-lg bg-base-200 text-base-content absolute z-9999",
|
|
menuList: () => "shadow-md rounded-lg",
|
|
option: () =>
|
|
"bg-base-200 text-base-content rounded-lg m-1 hover:bg-base-300 p-2",
|
|
noOptionsMessage: () =>
|
|
"p-2 bg-base-200 rounded-lg m-1 p-2"
|
|
}}
|
|
menuPortalTarget={document.getElementById(
|
|
"selectSignerModal"
|
|
)}
|
|
/>
|
|
</div>
|
|
<button
|
|
onClick={(e) => handleCreateNew(e, field.value)}
|
|
className="op-btn op-btn-accent op-btn-outline op-btn-sm"
|
|
>
|
|
<i className="fa-light fa-plus"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="flex gap-2 mx-4 mb-3">
|
|
<button
|
|
disabled={props?.isSubmit}
|
|
className="op-btn op-btn-primary op-btn-sm w-[80px]"
|
|
onClick={() => handleEmbedPrefill(props?.item)}
|
|
>
|
|
<span>{t("next")}</span>
|
|
</button>
|
|
<button
|
|
className="op-btn op-btn-ghost op-btn-sm"
|
|
onClick={() => props.navigatePageToDoc()}
|
|
>
|
|
<span>{t("edit-draft")}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</ModalUi>
|
|
<ModalUi
|
|
title={t("add-contact")}
|
|
isOpen={props.isNewContact.status}
|
|
handleClose={closePopup}
|
|
>
|
|
<AddContact
|
|
isDisableTitle
|
|
isAddYourSelfCheckbox
|
|
details={props.handleAddUser}
|
|
closePopup={closePopup}
|
|
newContactId={props.isNewContact.id}
|
|
/>
|
|
</ModalUi>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default PrefillWidgetModal;
|