import React, { useEffect, useLayoutEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { RootState } from '../../../store/store' import api from '../../../config/api'; import RightSideBar from './rightSideBar/RightSideBar'; import { DroppedItem } from '../../../utlity/types'; import { useNavigate, useParams } from 'react-router-dom'; import { IoMdClose } from 'react-icons/io'; import { fetchRecipients } from '../../../store/slices/selectedRecipientsSlice'; import { fetchDocuments } from '../../../store/slices/documentSlice'; import { FaQuestionCircle } from 'react-icons/fa'; import typeToIconMap from '../../../helper/iconMap'; import { IoAdd } from 'react-icons/io5'; import Loader from '../../dashboard/Loader'; export interface DroppedItemsObj { [key: string]: DroppedItem[] } interface DroppedTagsDefaultFieldType { [key: string]: string } const droppedTagsDefaultField: DroppedTagsDefaultFieldType = { text: "paragraph", temporal: "date", number: "integer", // image: "STAMP", select: "single select", checkbox: "TOGGLE" } interface DocumentContainerProps { isSend: boolean | null; setIsSend: React.Dispatch>; } const DocumentContainer: React.FC = ({ isSend }) => { const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>(); if (!accountId || !transactionId) { window.location.href = "/"; return null; } // const transactionId = useSelector((state: RootState) => state.transaction.transactionId) const navigate = useNavigate(); const dispatch = useDispatch(); const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients); // const selectedDocuments = useSelector((state: RootState) => state.selectedDocuments.selectedDocuments) const selectedDocuments = useSelector((state: RootState) => state.documents.documents) useEffect(() => { selectedRecipients.length === 0 && dispatch(fetchRecipients({ accountId, transactionId })); selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId })); }, []) // FETCHING TAG - START useEffect(() => { if (selectedRecipients.length === 0) return; const fetchTags = async () => { try { const requests = selectedRecipients.map((recipient) => api.get(`/api/accounts/${accountId}/transactions/${transactionId}/recipients/${recipient.id}/tags`) ); const response = await Promise.all(requests); let recTags = {} for (const res of response) { for (const tag of res.data) { const key = `${tag.documentId}_${tag.pageIndex}`; if (recTags[key]) { recTags[key].push(tag); } else { recTags[key] = [tag]; // Initialize with an array } } } // console.log(recTags, "Line 83"); setDroppedItems(recTags) // Handle your response here } catch (err) { console.error(err); } }; fetchTags(); }, [selectedRecipients]); // FETCHING TAG - END const [droppedItems, setDroppedItems] = useState({}); const [droppedItemsCounter, setDroppedItemsCounter] = useState(1); const [selectedTag, setSelectedTag] = useState<{ key: string, index: number } | null>(null); // NAVIGATING to Dashboard if Transaction is not created - START useLayoutEffect(() => { if (transactionId) return; navigate("/"); }, [transactionId]) // NAVIGATING to Dashboard if Transaction is not created - END const groupByRecipientId = (tagsArray: any[]) => { const tagsData: Record = {}; for (const tag of tagsArray) { const recipientId = tag.recipientId; if (!tagsData[recipientId]) { tagsData[recipientId] = []; } tagsData[recipientId].push(tag); } return tagsData; }; const sendTagsToRecipients = async ( tagsData: Record, accountId: string, transactionId: string ) => { // tagsData = tagsData.filter((tag, indx) => (("update" in tag && tag.update) || !("id" in tag)) ) // Filter Tags that needs to update const updatedData = {} // debugger for (const [recipientId, tags] of Object.entries(tagsData)) { console.log(recipientId, tags, "line 133") const updateTags = [] for (const tag of tags) { if ("update" in tag && tag.update) { updateTags.push(tag) } if (updateTags.length && "update" in updateTags[updateTags.length - 1]) { delete updateTags[updateTags.length - 1].update } } updatedData[recipientId] = updateTags } const requests = Object.entries(updatedData).map(([recipientId, tags]) => { return api.put( `/api/accounts/${accountId}/transactions/${transactionId}/recipients/${recipientId}/tags`, tags ); }); try { const responses = await Promise.all(requests); const droppedItemsUpdateFalse = Object.fromEntries( Object.entries(droppedItems).map(([recipientId, tags]) => { return [recipientId, tags.map(tag => ({ ...tag, update: false }))]; }) ) setDroppedItems(droppedItemsUpdateFalse); } catch (error) { console.error("Error submitting recipient tags:", error); } }; // Save Tags - START useEffect(() => { if (isSend === null) return; // dispatch(showLoader("Creating Transaction")) let updatedTags = { ...droppedItems } // const sendTags = { // "mod": [], // "remove": [] // } let tagsData = [] // const modified = [] // const remove = [] // console.log(updatedTags, "updatedTags"); for (const dropPageIds of Object.keys(updatedTags)) { for (const dropTag of updatedTags[dropPageIds]) { let mask = null console.log(dropTag, "line 132") // if (dropTag.subType?.toLowerCase() === "password") { // console.log(dropTag) mask = { "type": dropTag.maskType ? `${dropTag.maskType}`.toUpperCase() : null, "character": dropTag.maskCharacter ? `${dropTag.maskCharacter}`.toUpperCase() : null } // } console.log(dropTag, "line 204") const isDeleted = "isDeleted" in dropTag ? dropTag.isDeleted : false const updatedTagData = { "id": "id" in dropTag ? dropTag.id : null, "label": dropTag.label, "dimensions": dropTag.dimensions, "type": dropTag.type.toUpperCase(), // "subType": (dropTag.subType ? dropTag.subType.toUpperCase() : null), "minLength": "minLength" in dropTag ? dropTag.minLength : 0, "maxLength": "maxLength" in dropTag ? dropTag.maxLength : 0, "error": ("error" in dropTag && dropTag.error) ? `${dropTag.error}`.toUpperCase() : "error", "regex": "regex" in dropTag ? dropTag.regex : [] // } , "documentId": dropTag.documentId, "options": ("options" in dropTag) ? dropTag.options : [], "pageIndex": dropTag.pageIndex, "recipientId": dropTag.recipientId, "styles": "styles" in dropTag ? dropTag.styles : { "font": "", "italic": false, "bold": false, "underline": false, "color": "black", "textSize": 0 }, // "required": (dropTag.type.toUpperCase() !== "SIGNATURE" && dropTag.type.toUpperCase() !== "INITIALS") ? dropTag.required : true, "required": "required" in dropTag ? dropTag.required : true, "update": "update" in dropTag ? dropTag.update : false, "readOnly": false, } if ("format" in dropTag) { updatedTagData.format = `${dropTag.subType}`.toUpperCase(); updatedTagData.dateFormat = dropTag.format; updatedTagData.currentTimeStamp = dropTag.currentTimeStamp } if (`${dropTag.type}`.toLowerCase() === "number") { updatedTagData.decimalPlace = dropTag.decimalPlace updatedTagData.isCurrency = dropTag.isCurrency } if (updatedTagData.type === "TEXT") { updatedTagData.multiLine = dropTag.subType === "paragraph" if (dropTag.subType === "password") { updatedTagData.maskCharacter = mask.character updatedTagData.maskType = mask.type } } // if ("required" in updatedTagData && !updatedTagData.required) delete updatedTagData.required; if (updatedTagData.subType === 'SINGLE SELECT') updatedTagData.subType = "SINGLE_SELECT" if (updatedTagData.subType === 'MULTI SELECT') updatedTagData.subType = "MULTI_SELECT" if (updatedTagData.options && !updatedTagData.options.length) delete updatedTagData.options; if (!updatedTagData.id) delete updatedTagData.id; if (!isDeleted) { // sendTags.mod.push(updatedTagData); tagsData.push(updatedTagData) } } } // for(const recipient of selectedRecipients) { // const recrecipient // } tagsData = groupByRecipientId(tagsData); sendTagsToRecipients(tagsData, accountId, transactionId); (async () => { const res = await api.put(`/api/accounts/${accountId}/transactions/${transactionId}`, {status: "SENT"}) console.log(res, "Check123") })() }, [isSend]) // Save Tags - END const handleDragOver = (event: React.DragEvent) => { event.preventDefault(); // Allows the drop event to work }; // HANDLE DROP - START const handleDrop = (event: React.DragEvent, 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] || ; let prepData = { properties: data.properties, icon: typeToIconMap[field.label.toUpperCase()] || , type: dragElemData.label, 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 (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, 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); } setDroppedItemsCounter((prev) => prev + 1); setTimeout(() => { const tagIndex = key in droppedItems ? droppedItems[key].length : 0; handleTagClick(key, tagIndex) }, 0) }; // HANDLE DROP - END // HANDLE TAG MOUSE DOWN - START const handleTagMouseDown = (event: React.MouseEvent, key: string, index: number) => { event.preventDefault(); let targetContainer = event?.currentTarget?.parentElement?.parentElement; let targetElement = event?.currentTarget?.parentElement; 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; 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)); // Apply the movement to the element setDroppedItems({ ...droppedItems, [key]: Array.isArray(droppedItems[key]) // Ensure it's an array ? droppedItems[key].map((item, i) => i === index ? { ...item, dimensions: { ...item.dimensions, left, top }, update: true } : item ) : [], // Provide a fallback in case it's undefined or not an array }); }; 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 // HANDLE EXPAND TAG - START const handleExpandTag = (event: React.MouseEvent, key: string, index: number) => { console.log("HANDLE EXPAND TAG - START") event.preventDefault(); // Prevent text selection issues const expandingElemIcon = event.currentTarget; const targetElement = expandingElemIcon.nextElementSibling?.parentElement; const targetContainer = targetElement?.parentElement; if (!targetContainer || !targetElement) return; const elementRect = targetElement.getBoundingClientRect(); const startX = event.clientX; const startY = event.clientY; const startWidth = elementRect.width; const startHeight = elementRect.height; const handleMouseMove = (event: MouseEvent) => { let newWidth = startWidth + (event.clientX - startX); let newHeight = startHeight + (event.clientY - startY); if (newHeight > 200 || newWidth > 400) { if (newHeight > 200) { newHeight = 200 } if (newWidth > 400) { newWidth = 400 } } // Apply the new width & height setDroppedItems({ ...droppedItems, [key]: Array.isArray(droppedItems[key]) // Ensure it's an array ? droppedItems[key].map((item, i) => i === index ? { ...item, dimensions: { ...item.dimensions, width: newWidth, height: newHeight }, update: true } : item ) : [], // Provide a fallback in case it's undefined or not an array }); }; const handleMouseUp = () => { document.removeEventListener("pointermove", handleMouseMove); document.removeEventListener("pointerup", handleMouseUp); }; document.addEventListener("pointermove", handleMouseMove); document.addEventListener("pointerup", handleMouseUp); }; // HANDLE EXPAND TAG - END const handleTagClick = (key: string, index: number) => { setSelectedTag({ key, index }) } const handleRemoveTag = (key: string, targetIndex: number) => { const tag = droppedItems[key][targetIndex]; const recipientId = tag.recipientId; (async () => { if ("id" in tag) { try { const response = await api.delete(`/api/accounts/${accountId}/transactions/${transactionId}/recipients/${recipientId}/tags/${tag.id}`) if (response.status !== 200) throw "Tag Deletion error" } catch (err) { console.error("Tag Deletion error") return; } } setSelectedTag(null) setDroppedItems(prevState => ({ ...prevState, [key]: prevState[key].map((item, index) => index === targetIndex ? { ...item, isDeleted: true } : item ) })); })() } const handleAddRadioClick = (key:string) => { console.log("Handle Radio Clicled", key) console.log(droppedItems, "line 525") } // const handleAddRadio = (e: React.MouseEvent, key: string, index: number) => { // // const dataToSet: DroppedItemsObj = { // // ...droppedItems, // // [key]: [...(droppedItems[key] || []), // // { // // ...prepData, // // radioGroups: dragElemData.label === "radio" ? [{ ...prepData }] : [] // // }], // // } // // const lastRadioElemRect = e.currentTarget?.parentElement.previousSibling.getBoundingClientRect(); // // console.log(lastRadioElemRect, "lastRadioElemRect") // const setNewData = droppedItems; // if (Array.isArray(setNewData[key][index].radioGroups) && setNewData[key][index].radioGroups?.length > 0) { // setNewData[key][index].radioGroups?.push(setNewData[key][index].radioGroups[0]) // } // setDroppedItems(setNewData); // }; // const handleRadioTagMouseDown = (event: React.MouseEvent, key: string, index: number, radioIndex: number) => { // event.preventDefault(); // let targetContainer = event?.currentTarget?.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 handleMouseMove = (event: PointerEvent) => { // // Get new X and Y positions relative to the container // // let x = offsetX; // // let y = offsetY; // let x = event.clientX - elementRect.left; // let y = event.clientY - elementRect.top; // const newData = droppedItems; // if (newData[key][index].radioGroups && newData[key][index].radioGroups.length > 0) { // newData[key][index].radioGroups[radioIndex].dimensions.left = x; // newData[key][index].radioGroups[radioIndex].dimensions.top = y; // let topMin = Number.POSITIVE_INFINITY; // let topMax = Number.NEGATIVE_INFINITY; // let leftMin = Number.POSITIVE_INFINITY; // let leftMax = Number.NEGATIVE_INFINITY; // for (const radio of newData[key][index].radioGroups) { // if (radio.dimensions.left < leftMin) { // leftMin = radio.dimensions.left; // } // if (radio.dimensions.left > leftMax) { // leftMax = radio.dimensions.left; // } // if (radio.dimensions.top < topMin) { // topMin = radio.dimensions.top; // } // if (radio.dimensions.top > topMax) { // topMax = radio.dimensions.top; // } // } // newData[key][index].radioPosition = { // topMin: topMin, // topMax: topMax, // leftMin: leftMin, // leftMax: leftMax, // width: leftMax - leftMin, // height: topMax - topMin // } // newData[key][index].dimensions.left = x; // newData[key][index].dimensions.top = y; // } // setDroppedItems({ ...newData }); // }; // const handleMouseUp = () => { // document.removeEventListener("pointermove", handleMouseMove); // document.removeEventListener("pointerup", handleMouseUp); // }; // // Add event listeners // document.addEventListener("pointermove", handleMouseMove); // document.addEventListener("pointerup", handleMouseUp); // } // NEW DIRECTIONAL MOVEMENT HANDLER - START const handleDirectionalMove = (direction: 'up' | 'down' | 'left' | 'right', stepSize: number = 5) => { if (!selectedTag) return; const { key, index } = selectedTag; const currentItem = droppedItems[key]?.[index]; if (!currentItem) return; let newLeft = currentItem.dimensions.left; let newTop = currentItem.dimensions.top; switch (direction) { case 'up': newTop = Math.max(0, newTop - stepSize); break; case 'down': newTop = newTop + stepSize; break; case 'left': newLeft = Math.max(0, newLeft - stepSize); break; case 'right': newLeft = newLeft + stepSize; break; } setDroppedItems({ ...droppedItems, [key]: Array.isArray(droppedItems[key]) ? droppedItems[key].map((item, i) => i === index ? { ...item, dimensions: { ...item.dimensions, left: newLeft, top: newTop }, update: true } : item ) : [], }); }; // Create a wrapper that matches DirectionalControls' expected signature const directionalControlsHandler = (direction: 'up' | 'down' | 'left' | 'right') => { handleDirectionalMove(direction, 5); // Use default stepSize of 5 }; return ( <> {/* Center Doc Container - START */}
{ selectedDocuments.length > 0 ? selectedDocuments.map((doc, indxDoc) => { return (

{doc.name}

{ doc.images && doc.images.length > 0 && doc.images.map((page, indx) => { const key = `${doc.id}_${page.index}`; return (
handleDrop(e, doc.id, indxDoc, page.index)} onDragOver={handleDragOver} className='drop-page-in shadow-md relative' > {/* DROPPED ITEMS - Render only the dropped items for this specific docId and page - STARTS */} {droppedItems[key]?.map((item, index) => { if (item.isDeleted) return; const type = item.type.toUpperCase(); const IconComponent = typeToIconMap[type] || ; const selectedRecipient = selectedRecipients.filter((obj, indx) => obj.id === item.recipientId); return (
handleTagClick(key, index)} style={{ left: `${item.dimensions.left}px`, top: `${item.dimensions.top}px`, width: item.type === "radio" ? `${item.dimensions.width + 20}px` : `${item.dimensions.width}px`, height: item.type === "radio" ? `max-content` : `${item.dimensions.height}px` }} > {/* If Selected Tag Recipient Info- START */} {selectedTag?.key && selectedTag.key === key && selectedTag.index === index &&

{IconComponent}

{item.label}

{ e.stopPropagation(); handleRemoveTag(key, index) }} className='cursor-pointer' />
} {/* If Selected Tag Recipient Info- END */} {/* Expand Circle - STARTS */} {item.type !== "radio" &&
handleExpandTag(e, key, index)} className='w-2.5 h-2.5 absolute bg-red-400 rounded-full bottom-[-8px] right-[-8px] cursor-nw-resize hover:bg-blue-400'>
} {/* Expand Circle - ENDS */} { item.type !== "radio" ?
handleTagMouseDown(e, key, index)} // style={{ width: `${item.width}px !important` }} className='p-2 rounded-sm h-full shadow opacity-40 cursor-grab w-full' style={{ background: selectedRecipient[0]?.color }} >

{IconComponent}

: <>
{/* Radio - Container - START */}
{/* Radio- Item - START */}
handleTagMouseDown(e, key, index)} // style={{ width: `${item.width}px !important` }} className='p-2 rounded-sm h-full shadow opacity-40 cursor-grab w-full' style={{ background: selectedRecipient[0]?.color }} >

{IconComponent}

{/* Radio- Item - END */}
{/* Radio - Container - END */} handleAddRadioClick(key)} className='ml-[50%] translate-x-[-50%] cursor-pointer' />
}
) } )} {/* Render only the dropped items for this specific docId and page - ENDS */} {/* Side details of documents Pages and document Name - START */}

page {indx + 1} of {doc.images && doc.images.length}

{/*

Document {indxDoc + 1} {doc.name}

*/} {/* Side details of documents Pages and document Name - END */}
) }) }
) }) : }
{selectedTag && } {/* Center Doc Container - END */} ) } export default DocumentContainer;