SME-Web/src/components/client/SignDocument.tsx

1278 lines
68 KiB
TypeScript

import { useEffect, useState } from "react";
import axios from "axios"
import { RootState } from "../../store/store";
import { useParams } from "react-router-dom"
import { DroppedItem, DroppedItemsObj } from "../../utlity/types";
import Loader from "../dashboard/Loader";
import SignCon from "../signature/SignCon";
import { Bounce, toast } from "react-toastify";
import { useDispatch, useSelector } from "react-redux";
import { hideLoader, showLoader } from "../../store/slices/loaderSlice";
import { FaRegImage, FaFileAlt, FaSignature, FaStamp, FaCheck, FaQuestionCircle } from 'react-icons/fa'
import { IoIosDocument } from 'react-icons/io'
import { MdKeyboardArrowDown } from 'react-icons/md'
import { TbChevronsDownLeft, TbSignature } from "react-icons/tb";
import { BsFileArrowDownFill } from "react-icons/bs";
import dayjs from "dayjs";
import maskUtils from "../../utlity/maskUtils";
import StampUpload from "../stamp/StampUpload";
import TermsModal from "./TermsModal";
import DocumentPages from "./DocumentPages";
import FieldList from "../process/processStepTwo/FieldList";
import typeToIconMap from "../../helper/iconMap";
import { droppedTagsDefaultField } from "../process/processStepTwo/DocumentContainer";
interface RecipientType {
id: string;
firstName: string | null;
lastName: string;
fullName: string | null;
email: string;
title: string | null;
initials: string | null;
company: string | null;
website: string | null;
phone: string | null;
index: number;
type: string;
recipientStatus: string;
authType: string | null;
language: string | null;
transaction: string | null;
acceptedTerms: boolean;
name: string | null;
}
interface ImagesType {
index: number;
type: string;
src: string;
height: number;
width: number;
}
interface DocumentType {
documentId: string;
images: ImagesType[];
name: string;
}
interface TransactionType {
status: string;
transationId: string;
}
interface TagSubmitData {
id: string;
type?: string;
subType: string;
signed: boolean;
value: string;
}
interface TagSubmitRecData {
recipientId: string;
}
export enum SaveTypeEnum {
DRAW = "DRAW",
TYPE = "TEXT",
UPLOAD = "IMAGE",
}
export interface SaveType {
saveType: SaveTypeEnum | null;
save: boolean;
signature: string | null;
initial: string | null;
}
interface TagsSubmitDataObj {
tags: TagSubmitData[];
recipient?: TagSubmitRecData
}
interface SignConfType {
accepted: boolean;
index: number;
}
interface SignConfTypeObj {
[key: number]: SignConfType;
}
export interface StampTypeObj {
open: boolean;
blob: string;
}
interface FIT {
label: string,
value: number | null
}
const fitObj = [
{
label: "Fit",
value: "FIT"
},
{
label: "50%",
value: "50"
},
{
label: "150%",
value: "150"
},
{
label: "200%",
value: "200"
},
]
// Functions - LIST
// handleSignClick - Handle when sign || Initial tag got clicked
// fetchData - Fetch document images and tags data
// handleFormSubmit - handle form submit and send data to server
const SignDocument = () => {
const { recipientId } = useParams();
const dispatch = useDispatch();
// Signer data - START
const [selectedDocuments, setSelectedDocuments] = useState<DocumentType[] | null>(null);
const [recipient, setRecipient] = useState<RecipientType | null>(null);
const [tagJson, setTagJson] = useState<DroppedItem[] | []>([]);
const [transaction, setTransaction] = useState<TransactionType | null>(null);
const [recSignStatus, setRecSignStatus] = useState<string | null>(null);
const [droppedItems, setDroppedItems] = useState([]);
const [stamp, setStamp] = useState<StampTypeObj>({ open: false, blob: "" });
console.log(recipient, "recipient Line 145")
// Handling Date Opacity
const [dateValues, setDateValues] = useState<{ [key: string]: string }>({});
const [isDateOpen, setIsDateOpen] = useState<string | null>(null);
// Signer data - END
// Show is fields - Showing all fields There
const [isFields, setIsFields] = useState<boolean>(true);
const [isFitDd, setIsFitDd] = useState<boolean>(false)
const [selFitIndx, setSelFitIndx] = useState<number>(0);
const globalLoader = useSelector((state: RootState) => state.loader)
const [openSign, setOpenSign] = useState<boolean>(false);
const [focusElement, setFocusElement] = useState<DroppedItem | null>(null);
const [dropDownTogglePage, setDropDownTogglePage] = useState<string>("");
const [nav2DocToggle, setNav2DocToggle] = useState<boolean>(false);
const [hideTags, setHideTags] = useState<boolean>(false);
const [transparentTags, setTransparentTags] = useState<boolean>(false);
const [saved, setSaved] = useState<SaveType>({
saveType: null,
save: false,
signature: null,
initial: null,
});
const [signConfObj, setSignConfObj] = useState<SignConfTypeObj | {}>({});
const zoom = (percentage: number, contentClass: string) => {
const content = document.querySelector(`.${contentClass}`);
if (content) {
(content as HTMLElement).style.zoom = `${percentage}%`;
}
}
useEffect(() => {
let indx = fitObj[selFitIndx].value
if (indx === "FIT") {
indx = "100";
}
zoom(Number(indx), 'zoom-con')
}, [selFitIndx])
useEffect(() => {
dispatch(showLoader("Loading ..."));
fetchData();
}, [])
useEffect(() => {
if (!focusElement) {
if (droppedItems && droppedItems.length) {
setFocusElement({ ...droppedItems[0], focusElementIndx: 0 });
}
}
}, [droppedItems])
console.log(saved, "Line 162")
useEffect(() => {
if (recSignStatus === "COMPLETED") {
console.log("SETTING HIDE TAGS to TRUE LIne 161")
// setHideTags(true);
setTransparentTags(true);
} else {
console.log("SETTING HIDE TAGS to TRUE LIne 161")
setHideTags(false);
}
}, [recSignStatus])
useEffect(() => {
if (saved.save && Object.keys(signConfObj).length) {
const keys = Object.keys(signConfObj)
const index = Number(keys[0])
const newObj = {
accepted: true,
index: index
}
setSignConfObj((prev) => {
return (
{ ...prev, [index]: { ...newObj } }
)
});
}
}, [saved])
const handleSignClick = (index: number) => {
if (saved.save) {
if (index in signConfObj) {
setOpenSign(true);
return
};
setSignConfObj({
...signConfObj,
[index]: {
accepted: true,
index: index
}
})
return
}
setOpenSign(true);
const newObj = {
[index]: {
"accepted": false,
index: index
}
}
setSignConfObj({ ...newObj })
}
const handleStampClick = (index: number) => {
console.log("Stamp Clicked", index)
setStamp({
open: true,
blob: ""
})
}
const handleSignAcknowledgeClick = (index: number) => {
if (saved.save && signConfObj === null) {
setSignConfObj([...signConfObj, index])
}
}
const fetchData = (isSign: boolean = false) => {
axios.get(`${import.meta.env.VITE_BASE_URI}/api/sign?recipientId=${recipientId}`,
{
headers:
{ "ngrok-skip-browser-warning": "69420", }
})
.then((res) => {
console.dir(res)
if (res.status === 200) {
console.log(res, "Line 288")
setSelectedDocuments(res.data.documents)
setRecipient(res.data.recipient)
setDroppedItems(res.data.tagJsons)
setTransaction(res.data.transaction)
setRecSignStatus(res.data.status)
setTimeout(() => {
dispatch(hideLoader());
}, 600);
}
})
.catch((err) => {
console.error(err.message)
});
}
console.log(droppedItems, "Line 261")
const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const submitData: TagsSubmitDataObj = {
tags: []
}
if (!droppedItems) return;
// const form = e.currentTarget;
// const formData = new FormData(form);
// / Convert FormData to an object
// const data: { [key: string]: FormDataEntryValue } = {};
// formData.forEach((value, key) => {
// data[key] = value;
// });
let signInitialsCount = 0;
for (let i = 0; i < droppedItems.length; i++) {
if (droppedItems[i].type === "SIGNATURE" || droppedItems[i].type === "INITIALS") {
signInitialsCount += 1;
}
}
console.log(signInitialsCount, Object.keys(signConfObj).length, "boom")
if (signInitialsCount !== Object.keys(signConfObj).length) {
toast.error("Please ensure all signature and initials are signed", {
position: "bottom-right",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
return;
}
for (const tag of droppedItems) {
console.log(tag, "Tag Data")
// if ((tag.type === "SIGNATURE" || tag.subType === "INITIALS") && !tag.value) {
if (("required" in tag && tag.required) && ((tag.type !== "SIGNATURE" && tag.type !== "INITIALS") && (tag.value === "" || !("value" in tag)))) {
toast.error(`${tag.label} is required`, {
position: "bottom-right",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
return;
}
if (tag.type === "SIGNATURE" || tag.type === "INITIALS") {
const tagData: TagSubmitData = {
id: tag.id as string,
type: tag.type,
subType: saved.saveType as string,
signed: true,
value: ""
}
submitData.tags.push(tagData)
} else {
const tagData: TagSubmitData = {
id: tag.id as string,
type: tag.type,
subType: tag.subType ? tag.subType : "",
signed: true,
value: tag.value ? tag.value as string : ""
}
submitData.tags.push(tagData);
}
}
// for (const tag of tagJson) {
// if (data[tag.id as string]) {
// const tagData: TagSubmitData = {
// id: tag.id as string,
// type: tag.type,
// subType: tag.subType ? tag.subType : "",
// signed: true,
// value: data[tag.id as string] as string
// }
// submitData.tags.push(tagData)
// }
// if (tag.type === "SIGNATURE") {
// submitData.tags.push({
// id: tag.id as string,
// type: "SIGNATURE",
// subType: "DRAW",
// signed: true,
// value: ""
// })
// }
// if (tag.type === "INITIALS") {
// submitData.tags.push({
// id: tag.id as string,
// type: "INITIALS",
// subType: "DRAW",
// signed: true,
// value: ""
// })
// }
// }
dispatch(showLoader("Signing ..."));
axios.post(`${import.meta.env.VITE_BASE_URI}/api/sign?recipientId=${recipientId}`, submitData, { headers: { "ngrok-skip-browser-warning": "69420", } })
.then((res) => {
console.group("Submitting Sign Details");
console.dir(res);
console.groupEnd();
if (res.status === 200) {
setTransparentTags(true)
setTimeout(() => {
dispatch(hideLoader())
toast.dismiss();
toast.info("Signed Successfully!", {
position: "bottom-right",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
}, 800)
}
})
.catch((err) => {
console.error(err.message)
}
)
};
const handleTagNavClick = (action: "N" | "P") => {
if (!tagJson || !tagJson.length) return
// If FocusElement is Null Setting up 0 th Tag
if (!focusElement) {
setFocusElement({ ...tagJson[0], focusElementIndx: 0 });
return;
}
// If FocusElement is present updating in Based on Next and Prev
const currFocusElemIndx = focusElement.focusElementIndx;
if (currFocusElemIndx === undefined) return;
const updIndx = (action === "N") ? (currFocusElemIndx + 1) : (currFocusElemIndx - 1)
if (updIndx >= tagJson.length || updIndx < 0 || !tagJson[updIndx].id) return
scrollToElement(tagJson[updIndx].id)
setFocusElement({ ...tagJson[updIndx], focusElementIndx: updIndx })
}
const scrollToElement = (elementId: string) => {
const element = document.getElementById(elementId);
if (element) {
element.scrollIntoView({
behavior: "smooth", // You can adjust this to "auto" if you don't want smooth scrolling
block: "center" // Align the element in the center of the viewport
});
}
}
// const handleSelectOptClick = (tagId: string | undefined, optionIndx: number) => {
// if (!tagId) return;
// const selectedTag = tagJson?.filter((val, indx) => val.id === tagId)
// if (selectedTag && selectedTag?.length && "options" in selectedTag[0] && selectedTag[0]["options"]) {
// selectedTag[0].label = selectedTag[0]["options"][optionIndx]
// setTagJson(tagJson)
// }
// }
const fetchSignatureData = async (signatureId: string) => {
try {
const response = await axios.get(`${import.meta.env.VITE_BASE_URI}/api/sign/signatures/${signatureId}`, {
headers: { "ngrok-skip-browser-warning": "69420" }
});
if (response.status === 200) {
return response.data.signature;
}
} catch (error) {
console.error("Error fetching signature data:", error);
}
return null;
}
useEffect(() => {
(async () => {
if (droppedItems) {
for (const tag of droppedItems) {
if (tag.type === "SIGNATURE" && "value" in tag) {
const signatureData = await fetchSignatureData(tag.value);
console.log(signatureData);
}
}
}
})()
}, [droppedItems])
const handleTagUpdate = (updateIndx: number, updateValue: string) => {
if (droppedItems && droppedItems.length) {
const updatedTag = droppedItems.map((val, indx) => {
if (indx === updateIndx) {
return {
...val,
value: updateValue
}
}
return val;
})
setDroppedItems(updatedTag);
}
// setTagJson((prev) => {
// const newTagJson = [...prev];
// if (newTagJson[updateIndx]) {
// newTagJson[updateIndx].label = updateValue;
// }
// return newTagJson;
// });
}
// HANDLE DROP - START
const handleDrop = (event: React.DragEvent<HTMLDivElement>, docId: string, documentId: number, pageIndex: number) => {
console.log("// HANDLE DROP - START")
event.preventDefault();
const data = window.dragElemData;
if (!data) return;
const dragElemData = JSON.parse(data); // Get the dropped label
const field = { ...dragElemData }
if (!field.label) return;
// const field = fields.find((item) => item.label === dragElemData.label); // Find corresponding field
// if (!field) return; // If not found, exit
const dropZone = event.currentTarget.getBoundingClientRect();
const x = event.clientX - dropZone.left - dragElemData.offsetX;
const y = event.clientY - dropZone.top - dragElemData.offsetY;
const key = `${docId}_${pageIndex}`;
// const IconComponent = typeToIconMap[field.type] || <FaQuestionCircle />;
let prepData = {
properties: data.properties,
icon: typeToIconMap[field.label.toUpperCase()] || <FaQuestionCircle />,
type: dragElemData.label.toUpperCase(),
subType: (dragElemData.label in droppedTagsDefaultField) ? droppedTagsDefaultField[dragElemData.label] : '',
label: dragElemData.label,
dimensions: {
left: x,
top: y,
width: dragElemData.width,
height: dragElemData.height,
},
styles: dragElemData.label === "text" ? {
"font": "Great Vibes",
"italic": false,
"bold": false,
"underline": false,
"color": "black",
"textSize": 14.0
} : null,
documentId: docId,
pageIndex: pageIndex,
recipientId: dragElemData.recipientId,
recipientIndx: dragElemData.recipientIndx,
update: true,
isDeleted: false
}
if ("group" in dragElemData) {
prepData.group = dragElemData.group;
prepData.isGroupExist = false;
// if(uniqGroupIds.includes(dragElemData.group)) {
// prepData.isGroupExist = true;
// } else {
// prepData.isGroupExist = false;
// setUniqGroupIds([...uniqGroupIds, dragElemData.group]);
// }
}
if (prepData.type === "temporal") {
prepData.currentTimeStamp = true;
}
if (prepData.type.toLowerCase() === "number") {
prepData.decimalPlace = 0;
prepData.isCurrency = false;
}
// const dataToSet: DroppedItemsObj = {
// ...droppedItems,
// [key]: [...(droppedItems[key] || []),
// prepData],
// }
// const dataToSet: DroppedItemsObj = {
// ...droppedItems,
// [key]: [...(droppedItems[key] || []),
// {
// ...prepData,
// radioGroups: dragElemData.label === "radio" ?
// [
// {
// ...prepData,
// dimensions: {
// ...prepData.dimensions, top: 8, left: 10
// }
// },
// {
// ...prepData, dimensions: {
// ...prepData.dimensions,
// top: 8,
// left: 10
// }
// }
// ]
// :
// []
// }],
// }
// if (dragElemData.label === "radio") {
// setDroppedItems(dataToSet);
// } else {
// setDroppedItems(dataToSet);
// }
setDroppedItems((prev) => [...prev, prepData]);
// setDroppedItemsCounter((prev) => prev + 1);
// setTimeout(() => {
// const tagIndex = key in droppedItems ? droppedItems[key].length : 0;
// handleTagClick(key, tagIndex)
// }, 0)
};
// HANDLE DROP - END
const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault(); // Allows the drop event to work
};
// HANDLE TAG MOUSE DOWN - START
const handleTagMouseDown = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number) => {
event.preventDefault();
let targetContainer = event?.currentTarget?.parentElement?.parentElement;
let targetElement = event?.currentTarget;
if (!targetContainer || !targetElement) return;
const containerRect = targetContainer.getBoundingClientRect();
const elementRect = targetElement.getBoundingClientRect();
// Calculate the initial offset (where the mouse was clicked inside the element)
const offsetX = event.clientX - elementRect.left;
const offsetY = event.clientY - elementRect.top;
const conScrollWidth = targetContainer?.scrollWidth;
const conScrollHeight = targetContainer?.scrollHeight;
let isGroupAttr = event.currentTarget.hasAttribute("group");
let groupId = null;
if (isGroupAttr) {
groupId = event.currentTarget.getAttribute("group");
}
let groupElements: NodeListOf<HTMLElement> | null = null
if (isGroupAttr && groupId) {
groupElements = document.querySelectorAll(`div[group="${groupId}"]`);
}
const handleMouseMove = (event: MouseEvent) => {
// Get new X and Y positions relative to the container
let left = event.clientX - containerRect.left - offsetX;
let top = event.clientY - containerRect.top - offsetY;
// **Restrict movement within container bounds**
left = Math.max(0, Math.min(left, conScrollWidth - elementRect.width));
top = Math.max(0, Math.min(top, conScrollHeight - elementRect.height));
console.log(left, top, "LEFTTOP")
if (groupElements != null && groupElements.length) handleGroupBorderPosition(groupElements, containerRect, offsetX, offsetY);
// Apply the movement to the element
setDroppedItems(
droppedItems.map((item, i) =>
i === index ? { ...item, dimensions: { ...item.dimensions, left, top }, update: true } : item
)
);
};
const handleMouseUp = () => {
document.removeEventListener("pointermove", handleMouseMove);
document.removeEventListener("pointerup", handleMouseUp);
};
// Add event listeners
document.addEventListener("pointermove", handleMouseMove);
document.addEventListener("pointerup", handleMouseUp);
};
// HANDLE TAG MOUSE DOWN - END
console.log(droppedItems, "DROPPED ITEMS LINE 680")
// const [showTerms, setShowTerms] = useState<boolean>(true);
return (
<div className={`w-full h-screen overflow-hidden relative ${(globalLoader.isLoading) ? 'pointer-events-none overflow-hidden' : ''}`}>
<>
{/* <TermsModal showTerms={showTerms} setShowTerms={setShowTerms} recipientId={recipientId || ""} /> */}
<form className="bg-slate-100"
onSubmit={(e: React.FormEvent<HTMLFormElement>) => handleFormSubmit(e)}
>
{/* Nav Two - Options - START */}
<div className='flex justify-between items-center px-4 py-1 bg-white'>
{/* Left Side */}
<div className='flex justify-start items-center gap-1'>
<div className='flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm'>
<p className='tracking-wide font-semibold text-[1rem] text-[var(--primary)]'>Sign Made Easy</p>
</div>
{/* FIT - START */}
<div
tabIndex={1}
onBlur={() => setIsFitDd(false)}
>
<div
onClick={() => setIsFitDd(!isFitDd)}
className='flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm'>
<p className='tracking-wide'>Fit</p>
<MdKeyboardArrowDown style={{ transform: `${isFitDd ? "rotate(180deg)" : ""}` }} />
</div>
{isFitDd && <div className='absolute bg-white rounded-sm z-50'>
{fitObj.map((val, indx) => {
return (<div
key={indx}
onClick={() => setSelFitIndx(indx)}
className='flex z-50 items-center justify-between gap-2 w-[70px] p-2 text-xs cursor-default hover:bg-slate-100'>
<div
className='flex items-center justify-between w-full'>
<p className='whitespace-nowrap overflow-hidden text-ellipsis max-w-[12ch]'>{val.label}</p>
{selFitIndx === indx && <FaCheck className='text-sm text-slate-400' />}
</div>
</div>)
})
}
</div>}
</div>
{/* FIT - END */}
</div>
{/* Right Side - STARTS */}
<div className='flex justify-center items-center'>
{/* Documnt Nav */}
<>
{selectedDocuments !== null &&
<div tabIndex={1}
onBlur={() => setNav2DocToggle(false)}>
<div onClick={() => setNav2DocToggle(!nav2DocToggle)} className='flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm'>
<p className='tracking-wide'>{selectedDocuments && selectedDocuments.length} Documents</p>
<MdKeyboardArrowDown className={`${nav2DocToggle ? 'rotate-180' : ''} transition-all`} />
</div>
<div
className={`absolute max-w-max shadow rounded-md mt-1 px-2 py-1 z-20 bg-white ${nav2DocToggle ? '' : 'hidden'}`}>
{selectedDocuments.length && selectedDocuments.map((document, indx) => {
return (<div key={indx} className='flex items-center justify-between gap-2 w-full p-2 text-xs rounded-sm cursor-default'>
<div className='flex items-center gap-1'>
{/* <p className='w-3 h-3 rounded-sm' style={{ background: recipient.color }}></p> */}
<IoIosDocument className='text-slate-600 text-lg' />
<p className='whitespace-nowrap overflow-hidden text-ellipsis max-w-[15ch]'>{document.name}</p>
</div>
</div>)
})}
</div>
</div>
}
</>
{/* Send */}
{(!transparentTags && !hideTags) ? <button
// onClick={() => handleFormSubmitBtnClick()}
className='flex items-center gap-2 cursor-pointer text-xs px-4 py-2 ml-3 rounded-sm bg-[var(--primary)] outline-none'>
<p
className='tracking-wide text-white'>Sign</p>
<FaSignature className='text-white text-md' />
</button>
:
<p className="text-xs">Signed Successfully</p>
}
<p>{recipient && recipient.firstName} {recipient && recipient.lastName}</p>
</div>
{/* Right Side - ENDS */}
</div>
{/* Nav Two - Options - End */}
{/* Document Container Start */}
<div className="grid grid-cols-12 gap-1 mt-1 h-[calc(100vh-48px)] overflow-hidden">
{/* Left Side pages start */}
{/* <div className="lg:col-span-2 bg-slate-100 h-full p-4 overflow-y-auto"></div> */}
<div className='col-span-2 bg-white h-full p-4 overflow-y-auto'>
{recipientId && <FieldList color={"#8f87f1"} recipientId={Number(recipientId)} recipientIndx={0} acntId={"1"} />}
</div>
{/* Pages - END */}
{/* Main container */}
<div
id="doc-con"
className="zoom-con relative col-span-8 px-4 bg-slate-100 h-full w-full overflow-auto scroll-smooth"
>
{
selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((doc) => {
console.log(droppedItems, "tagJsontagJsontagJson")
return (
<div key={doc.documentId} className='flex flex-col'>
<div className='py-2 text-sm'>
<p className='text-center'>{doc.name}</p>
</div>
<div className='flex flex-col gap-3 max-w-max relative left-[50%] translate-x-[-50%]'>
{
doc.images && doc.images.length > 0 && doc.images.map((page, indx) => {
const key = `${doc.documentId}_${page.index}`;
console.log(droppedItems, "line 820")
// const tagJsonData = [...droppedItems];
// console.log(tagJsonData, "Line 821")
const currDocTags = droppedItems.filter((val) => val.pageIndex === page.index)
console.log(currDocTags, droppedItems, page, doc, "innnnnnnnnnnnn")
return (
<div
key={indx}
id={doc.documentId + indx}
onDrop={(e) => handleDrop(e, doc.documentId, doc.documentId, page.index)}
onDragOver={(e) => handleDragOver(e)}
className='drop-page-in shadow-md relative'
>
{!hideTags && <>
{currDocTags.map((item, index) => {
console.log(item, "micCheck 842")
if (item.pageIndex !== page.index) return;
return (
<div
tabIndex={1}
onFocus={() => setFocusElement({ ...item, focusElementIndx: index })}
key={item.id}
// draggable="false"
onPointerDown={(e) => handleTagMouseDown(e, key, index)}
className="absolute rounded dropped-item text-sm"
// onClick={() => handleTagClick(key, index)}
style={{
left: `${item.dimensions.left}px`,
top: `${item.dimensions.top}px`,
width: `${item.dimensions.width}px`,
height: `${item.dimensions.height}px`,
border: `${((!hideTags && focusElement) && !transparentTags && focusElement.documentId === item.documentId && focusElement.focusElementIndx === index) ? '2px dashed #1c398e' : 'none'}`,
// display: `${hideTags ? 'none' : 'block'}`
}}
>
{/* TEXT FIELD - START */}
{item.type === "TEXT" &&
<div
id={item.id}
tabIndex={1}
className={`h-full text-[14px] font-medium w-full border-slate-100 rounded ${(transparentTags || hideTags) ? "pointer-events-nones" : "bg-slate-200 shadow-sm opacity-80"} flex justify-start items-end`}
// style={{ height: item.dimensions.height + "px" }}
>
{/* <input
name={item.id}
className="w-full px-2 outline-none"
>
</input> */}
{
item.subType === "SINGLE_LINE" && <p
// name={item.id}
// style={{ height: item.dimensions.height + "px" }}
onInput={(e) => {
const val = e.currentTarget.textContent;
handleTagUpdate(index, val as string);
}}
onKeyDown={(e) => {
if (e.key === "Enter") e.preventDefault();
}}
contentEditable={true}
className="outline-none whitespace-nowrap w-full max-h-max"
></p>
}
{
item.subType === "PARAGRAPH" && <p
onInput={(e) => {
const val = e.currentTarget.textContent;
handleTagUpdate(index, val as string);
}}
contentEditable={true}
className="w-full outline-none max-h-max"
>
</p>
}
{
item.subType === "PASSWORD" && item.mask && <p
onInput={(e) => {
const val = e.currentTarget.textContent;
handleTagUpdate(index, val as string);
}}
onKeyDown={(e) => {
if (e.key === "Enter") e.preventDefault();
}}
onFocus={() => {
console.log("focus")
}}
onBlur={(e) => {
let val = e.target.textContent;
console.log(item.mask, val, "Line 586")
if (typeof item.mask?.maskType === "string" && typeof val === "string") {
val = val.replace(/ /g, "");
console.log("onBlur", item.mask.maskCharacter, maskUtils((item.mask as { maskType: string }).maskType, val, (item.mask as { maskCharacter: string }).maskCharacter))
e.target.textContent = maskUtils((item.mask as { maskType: string }).maskType, val, (item.mask as { maskCharacter: string }).maskCharacter)
}
}}
contentEditable={true}
className="outline-none whitespace-nowrap w-full max-h-max"
></p>
}
</div>}
{/* TEXT FIELD - END */}
{/* CHECKBOX FIELD - START */}
{item.type === "CHECKBOX" &&
<div
id={item.id}
className={`h-full w-full rounded flex justify-center items-center ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`}
// style={{ height: item.dimensions.height + "px" }}
>
<input
name={item.id}
// style={{ height: item.dimensions.height + "px" }}
className="w-full px-2 outline-none"
type="checkbox" />
</div>}
{/* CHECKBOX FIELD - END */}
{/* SIGNATURE FIELD - START */}
{item.type === "SIGNATURE" &&
<div
tabIndex={1}
id={item.id}
onClick={() => handleSignClick(index)}
className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none bg-transparent" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`}
// style={{ height: item.dimensions.height + "px" }}
>
{/* <input type="checkbox" className="" /> */}
{
(!transparentTags && !hideTags && !(saved.save && (index in signConfObj && signConfObj[index].accepted))) ?
<>
<FaSignature className="text-lg text-slate-600" />
</>
:
<img
style={{
width: `${item.dimensions.width}px`,
height: `${item.dimensions.height}px`,
}}
src={saved.signature as string} />
}
</div>}
{/* SIGNATURE FIELD - END */}
{/* INITIALS FIELD - START */}
{item.type === "INITIALS" &&
<div
tabIndex={1}
id={item.id}
onClick={() => handleSignClick(index)}
className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`}
// style={{ height: item.dimensions.height + "px" }}
>
{((!transparentTags && !hideTags) && !(saved.save && (index in signConfObj) && signConfObj[index].accepted)) ?
<TbSignature className="text-lg text-slate-600" />
:
<img
style={{
width: `${item.dimensions.width}px`,
height: `${item.dimensions.height}px`,
}}
className="w-[100%] h-full"
src={saved.initial as string} />
}
</div>}
{/* INITIALS FIELD - END */}
{/* STAMP FIELD - START */}
{item.type === "STAMP" &&
<div
tabIndex={1}
id={item.id}
onClick={() => handleStampClick(index)}
className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`}
// style={{ height: item.dimensions.height + "px" }}
>
{!transparentTags && !hideTags ?
<FaStamp className="text-lg text-slate-600" />
:
<img className="w-[100%] h-full" src={saved.initial as string} />
}
</div>}
{/* STAMP FIELD - END */}
{/* IMAGE FIELD - START */}
{item.type === "FILE" &&
<div
tabIndex={1}
id={item.id}
// onClick={() => handleSignClick(index)}
className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`}
// style={{ height: item.dimensions.height + "px" }}
>
{!transparentTags && !hideTags ?
<FaFileAlt className="text-lg text-slate-600" />
:
<img className="w-[100%] h-full" src={saved.initial as string} />
}
</div>}
{/* IMAGE FIELD - END */}
{/* SELECT FIELD - START */}
{item.type === "SELECT" &&
<div
id={item.id}
tabIndex={1}
className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100"}`}
>
<select
name={item.id}
className="outline-none"
>
<option value="">Select</option>
{item.options && item.options.map((val, indx) => {
return (
<option
key={indx}
value={val}
>
{val}
</option>
)
})
}
</select>
</div>}
{/* SELECT FIELD - END */}
{/* TEMPORAL FIELD - START */}
{item.type === "TEMPORAL" &&
<div
id={item.id}
tabIndex={1}
className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100"}`}
>
<p
style={{ opacity: `${item.id === isDateOpen ? "0" : "100"}` }}
className="w-full h-full rounded pl-1 pt-1 whitespace-nowrap">
{(item.id && item.id in dateValues) ? dateValues[item.id] : item.subType?.toLowerCase()}
</p>
<input
name={item.id}
onFocus={(e) => {
e.target.showPicker?.()
if (item.id) setIsDateOpen(item.id)
}}
onBlur={(e) => {
let val = e.target.value;
// Adding dummy time in case of time as it giving Invalid date without that
if (item.subType?.toLowerCase() === "time") val = "09/02/2025 " + val
if (!item.id) return;
setIsDateOpen(null);
setDateValues({
...dateValues,
[item.id as string]: dayjs(val).format(item.format)
})
}}
style={{ opacity: `${item.id === isDateOpen ? "100" : "0"}` }}
className="w-full px-2 outline-none absolute"
onClick={() => {
}}
type={`${item.subType?.toLowerCase() === "date" ? "date" : (item.subType?.toLowerCase() === "time" ? "time" : "datetime-local")}`}
/>
</div>}
{/* TEMPORAL FIELD - END */}
{/* Focus Show Element */}
{(!transparentTags && !hideTags) && (focusElement && focusElement.documentId === item.documentId && focusElement.focusElementIndx === index) &&
<>
<div
className="absolute left-0 z-50 bg-blue-900 text-xs text-slate-100 px-2 py-[2px] rounded-sm whitespace-nowrap"
style={{ top: -23 + "px" }}
>
<p>{focusElement.label}</p>
</div>
<div
className="absolute left-[-10px] z-50 flex max-w-max"
style={{ left: -(item.dimensions.left + 20) + "px", top: ((item.dimensions.height / 2) - 18) + "px" }}
>
{/* <FaArrowRight className="text-[var(--primary)] text-xl" />
*/}
<BsFileArrowDownFill className="text-blue-900 text-4xl rotate-270" />
</div>
</>
}
</div>
)
})
}
</>}
<img
style={{ "minWidth": page.width, "minHeight": page.height, "maxWidth": page.width, "maxHeight": page.height }}
draggable="false"
width={page.width}
height={page.height}
src={page.src}
/>
{/* Side details of documents Pages and document Name - START */}
<p className='absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs left-[-60px] text-slate-600'>
page {indx + 1} of {doc.images && doc.images.length}
</p>
{/* Side details of documents Pages and document Name - END */}
</div>
)
})
}
</div>
</div>
)
})
}
{/* tag pagination Start */}
{/* {!transparentTags && !hideTags && <div className="sticky rounded-md bottom-2 left-[50%] translate-[-50%] max-w-max flex bg-slate-900 text-white text-sm opacity-70">
<div className="px-3 py-1 cursor-pointer hover:bg-black rounded-l-md transition-all duration-150 "
onClick={() => handleTagNavClick("P")}
>Prev</div>
<div className="px-3 py-1 border-l-1 border-r-1">{focusElement ? focusElement.label : "NotSelected"}</div>
<div className="px-3 py-1 cursor-pointer hover:bg-black rounded-r-md transition-all duration-150 "
onClick={() => handleTagNavClick("N")}
>Next</div>
</div>} */}
</div>
<DocumentPages selectedDocuments={selectedDocuments} setDropDownTogglePage={setDropDownTogglePage} dropDownTogglePage={dropDownTogglePage} />
{/* Right Side - START */}
{/* <div className="col-span-2 bg-slate-100 h-full p-4 overflow-y-auto">
{
focusElement && <>
</>
}
</div> */}
{/* Right Side - END */}
</div>
{/* Document Container End */}
</form>
</>
<div style={{ display: `${openSign ? 'block' : 'none'}` }}>
<SignCon recipientId={recipientId as string} setOpenSign={setOpenSign} saved={saved} setSaved={setSaved} />
</div>
<div style={{ display: `${stamp.open ? 'block' : 'none'}` }}>
<StampUpload stamp={stamp.blob} setStamp={setStamp} />
</div>
{/* <SignatureCon recipientId={recipientId as string} openSign={openSign} setOpenSign={setOpenSign} savedSignature={savedSignature} setSavedSignature={setSavedSignature} />
<InitialCon openInitial={openInitial} setOpenInitial={setOpenInitial} savedInitial={savedInitial} setSavedInitial={setSavedInitial} /> */}
{globalLoader.isLoading && <Loader />}
</div>
)
}
export default SignDocument