zoom fix + tag placement fix for process step two + expand and collapse tag

This commit is contained in:
Bhagwan Singh 2026-05-26 12:39:22 +05:30
parent 6efe737e50
commit 68c45678a3
11 changed files with 733 additions and 588 deletions

View File

@ -142,6 +142,22 @@ const fitObj = [
label: "200%", label: "200%",
value: "200" value: "200"
}, },
{
label: "250%",
value: "250"
},
{
label: "300%",
value: "300"
},
{
label: "350%",
value: "350"
},
{
label: "400%",
value: "400"
},
] ]
enum RightSideNav { enum RightSideNav {
@ -150,6 +166,7 @@ enum RightSideNav {
} }
const SignDocument = () => { const SignDocument = () => {
const windowWidth = window.innerWidth; const windowWidth = window.innerWidth;
const { recipientId } = useParams(); const { recipientId } = useParams();
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -158,6 +175,9 @@ const SignDocument = () => {
// Signer data - START // Signer data - START
const [selectedDocuments, setSelectedDocuments] = useState<DocumentType[] | null>(null); const [selectedDocuments, setSelectedDocuments] = useState<DocumentType[] | null>(null);
const [imageSizes, setImageSizes] = useState<Record<string, { width: number; height: number }>>({});
const [selectedTag, setSelectedTag] = useState<{ key: string, index: number } | null>(null); const [selectedTag, setSelectedTag] = useState<{ key: string, index: number } | null>(null);
const [isPages, setIsPages] = useState<boolean>(true); const [isPages, setIsPages] = useState<boolean>(true);
@ -208,6 +228,55 @@ const SignDocument = () => {
const [provideTags, setProvideTags] = useState(false) const [provideTags, setProvideTags] = useState(false)
const [showCompletionScreen, setShowCompletionScreen] = useState(false) const [showCompletionScreen, setShowCompletionScreen] = useState(false)
const [zoomScale, setZoomScale] = useState<number>(100)
const scaleFactor: number = (zoomScale ?? 100) / 100;
// To center the container on every zoom scale change - START
useEffect(() => {
const container = document.getElementById("doc-con");
if (!container) return;
const prevScrollRatio = container.scrollTop / (container.scrollHeight || 1);
const newScrollTop = prevScrollRatio * container.scrollHeight;
const newScrollLeft = (container.scrollWidth - container.clientWidth) / 2;
container.scrollTo({
top: newScrollTop,
left: newScrollLeft,
behavior: "instant" as ScrollBehavior
});
}, [zoomScale]);
// To center the container on every zoom scale change - END
// Handle Tags and Image Size - START
useEffect(() => {
const observers: ResizeObserver[] = [];
Object.keys(imageRefs.current).forEach((key) => {
const el = imageRefs.current[key];
if (!el) return;
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
setImageSizes((prev) => ({
...prev,
[key]: { width, height },
}));
}
});
observer.observe(el);
observers.push(observer);
});
return () => observers.forEach((o) => o.disconnect());
}, [selectedDocuments]); // re-run when documents load
// Handle Tags anf Image Size = END
const [saved, setSaved] = useState<SaveType>({ const [saved, setSaved] = useState<SaveType>({
signatureId: null, signatureId: null,
saveType: null, saveType: null,
@ -229,25 +298,31 @@ const SignDocument = () => {
const [signConfObj, setSignConfObj] = useState<SignConfTypeObj | {}>({}); const [signConfObj, setSignConfObj] = useState<SignConfTypeObj | {}>({});
const handleFitSelect = (indx: number) => {
setSelFitIndx(indx);
let value = fitObj[indx].value;
if (value === "FIT") value = "100";
setZoomScale?.(Number(value));
}
const zoom = (percentage: number, contentClass: string) => { // const zoom = (percentage: number, contentClass: string) => {
const content = document.querySelector(`.${contentClass}`); // const content = document.querySelector(`.${contentClass}`);
if (content) { // if (content) {
(content as HTMLElement).style.zoom = `${percentage}%`; // (content as HTMLElement).style.zoom = `${percentage}%`;
} // }
} // }
// useEffect(() => {
// let indx = fitObj[selFitIndx].value
// if (indx == "FIT") {
// indx = "100";
// }
// zoom(Number(indx), 'zoom-con')
// }, [selFitIndx])
const handleTagClick = (key: string, index: number) => { const handleTagClick = (key: string, index: number) => {
setSelectedTag({ key, index }) setSelectedTag({ key, index })
} }
useEffect(() => {
let indx = fitObj[selFitIndx].value
if (indx == "FIT") {
indx = "100";
}
zoom(Number(indx), 'zoom-con')
}, [selFitIndx])
useEffect(() => { useEffect(() => {
dispatch(showLoader("Loading ...")); dispatch(showLoader("Loading ..."));
@ -894,7 +969,8 @@ const SignDocument = () => {
{fitObj.map((val, indx) => { {fitObj.map((val, indx) => {
return (<div return (<div
key={indx} key={indx}
onClick={() => setSelFitIndx(indx)} // onClick={() => setSelFitIndx(indx)}
onClick={() => handleFitSelect(indx)}
className='flex z-50 items-center justify-between gap-2 w-[70px] p-2 text-xs cursor-default hover:bg-slate-100'> className='flex z-50 items-center justify-between gap-2 w-[70px] p-2 text-xs cursor-default hover:bg-slate-100'>
<div <div
@ -1087,6 +1163,22 @@ const SignDocument = () => {
id="doc-con" id="doc-con"
className={`zoom-con relative ${provideTags ? (isFields && selectedTag != null ? "col-span-8" : "col-span-10") : (isFields ? "col-span-12" : "col-span-10")} sm:px-4 bg-slate-100 h-full w-full overflow-auto scroll-smooth transition-all duration-200 pb-[4rem]`} className={`zoom-con relative ${provideTags ? (isFields && selectedTag != null ? "col-span-8" : "col-span-10") : (isFields ? "col-span-12" : "col-span-10")} sm:px-4 bg-slate-100 h-full w-full overflow-auto scroll-smooth transition-all duration-200 pb-[4rem]`}
> >
<div style={{
width: `${100 * scaleFactor}%`, // ← outer wrapper grows to make room
height: `${100 * scaleFactor}%`,
}}>
<div className="zoom-con"
style={{
transform: `scale(${scaleFactor})`,
transformOrigin: 'top left', // ← change to top left
width: `${100 / scaleFactor}%`,
height: scaleFactor < 1
? `${100 / scaleFactor}%` // zoomed out — expand to fill
: 'auto', // zoomed in — let content grow
}}>
{ {
selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((doc) => { selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((doc) => {
return ( return (
@ -1119,6 +1211,16 @@ const SignDocument = () => {
// style={{ "width": page.width, "height": page.height, "maxWidth": "100%", "maxHeight": "100%" }} // style={{ "width": page.width, "height": page.height, "maxWidth": "100%", "maxHeight": "100%" }}
className={`w-[${page.width / 2}px] h-[${page.height / 2}px] sm:w-[${page.width}px] sm:h-[${page.height}px]`} className={`w-[${page.width / 2}px] h-[${page.height / 2}px] sm:w-[${page.width}px] sm:h-[${page.height}px]`}
ref={(el) => (imageRefs.current[page.index] = el)} ref={(el) => (imageRefs.current[page.index] = el)}
onLoad={(e) => {
const el = e.currentTarget;
setImageSizes((prev) => ({
...prev,
[page.index]: {
width: el.offsetWidth,
height: el.offsetHeight,
},
}));
}}
draggable="false" draggable="false"
// onLoad={(imgElem) => { // onLoad={(imgElem) => {
// setPageScales((prev) => ({ // setPageScales((prev) => ({
@ -1137,10 +1239,11 @@ const SignDocument = () => {
{!hideTags && <> {!hideTags && <>
<TagsCon <TagsCon
zoomScale={zoomScale}
pageWidth={page.width} pageWidth={page.width}
pageHeight={page.height} pageHeight={page.height}
renderedWidth={imageRefs.current[page.index]?.offsetWidth || 0} renderedWidth={imageSizes[page.index]?.width || imageRefs.current[page.index]?.offsetWidth || 0}
renderedHeight={imageRefs.current[page.index]?.offsetHeight || 0} renderedHeight={imageSizes[page.index]?.height || imageRefs.current[page.index]?.offsetHeight || 0}
currDocTags={currDocTags} currDocTags={currDocTags}
pageIdkey={key} pageIdkey={key}
provideTags={provideTags} provideTags={provideTags}
@ -1221,6 +1324,8 @@ const SignDocument = () => {
</div>} </div>}
</div> </div>
</div>
</div>
{provideTags && selectedTag && selectedTag != null && "key" in selectedTag && "index" in selectedTag && {provideTags && selectedTag && selectedTag != null && "key" in selectedTag && "index" in selectedTag &&
<div className={`col-span-2 ${provideTags ? "" : "order-first"} h-full ${!isFields ? "bg-white" : isFields && provideTags ? "bg-white" : ""} overflow-y-auto`}> <div className={`col-span-2 ${provideTags ? "" : "order-first"} h-full ${!isFields ? "bg-white" : isFields && provideTags ? "bg-white" : ""} overflow-y-auto`}>
@ -1247,6 +1352,7 @@ const SignDocument = () => {
<div style={{ display: `${openSign ? 'block' : 'none'}` }}> <div style={{ display: `${openSign ? 'block' : 'none'}` }}>
{recipient && {recipient &&
<ClientSignCon <ClientSignCon
zoomScale={zoomScale}
recipient={recipient} recipient={recipient}
recipientId={recipientId as string} recipientId={recipientId as string}
setOpenSign={setOpenSign} saved={saved} setOpenSign={setOpenSign} saved={saved}

View File

@ -14,6 +14,7 @@ import axios from 'axios';
interface TagsConProps { interface TagsConProps {
zoomScale: number,
pageWidth: number, pageWidth: number,
pageHeight: number, pageHeight: number,
renderedWidth: number, renderedWidth: number,
@ -43,6 +44,7 @@ interface TagsConProps {
} }
const TagsCon: React.FC<TagsConProps> = ({ const TagsCon: React.FC<TagsConProps> = ({
zoomScale,
pageWidth, pageWidth,
pageHeight, pageHeight,
renderedWidth, renderedWidth,
@ -71,60 +73,76 @@ const TagsCon: React.FC<TagsConProps> = ({
handleTagNavClick handleTagNavClick
}) => { }) => {
const scaleFactor: number = (zoomScale ?? 100) / 100;
const [dateValues, setDateValues] = useState<{ [key: string]: string }>({}); const [dateValues, setDateValues] = useState<{ [key: string]: string }>({});
const [isDateOpen, setIsDateOpen] = useState<string | null>(null); const [isDateOpen, setIsDateOpen] = useState<string | null>(null);
const handleTagMouseDown = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number) => { const handleTagMouseDown = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number) => {
setSelectedTag({ key, index }) setSelectedTag({ key, index });
event.preventDefault(); event.preventDefault();
const scale = scaleFactor ?? 1; // zoom factor
let targetContainer = event?.currentTarget?.parentElement?.parentElement; let targetContainer = event?.currentTarget?.parentElement?.parentElement;
let targetElement = event?.currentTarget; let targetElement = event?.currentTarget?.parentElement;
if (!targetContainer || !targetElement) return; if (!targetContainer || !targetElement) return;
const containerRect = targetContainer.getBoundingClientRect(); const containerRect = targetContainer.getBoundingClientRect();
const elementRect = targetElement.getBoundingClientRect(); const elementRect = targetElement.getBoundingClientRect();
// Calculate the initial offset (where the mouse was clicked inside the element) const scaleX = renderedWidth && pageWidth ? renderedWidth / pageWidth : 1;
const offsetX = event.clientX - elementRect.left; const scaleY = renderedHeight && pageHeight ? renderedHeight / pageHeight : 1;
const offsetY = event.clientY - elementRect.top;
const conScrollWidth = targetContainer?.scrollWidth; // Click offset in screen pixels
const conScrollHeight = targetContainer?.scrollHeight; const clickOffsetX = event.clientX - elementRect.left;
const clickOffsetY = event.clientY - elementRect.top;
let isGroupAttr = event.currentTarget.hasAttribute("group"); let isGroupAttr = event.currentTarget.hasAttribute("group");
let groupId = null; let groupId = null;
if (isGroupAttr) { if (isGroupAttr) groupId = event.currentTarget.getAttribute("group");
groupId = event.currentTarget.getAttribute("group");
}
let groupElements: NodeListOf<HTMLElement> | null = null let groupElements: NodeListOf<HTMLElement> | null = null;
if (isGroupAttr && groupId) { if (isGroupAttr && groupId) {
groupElements = document.querySelectorAll(`div[group="${groupId}"]`); groupElements = document.querySelectorAll(`div[group="${groupId}"]`);
} }
const handleMouseMove = (event: MouseEvent) => { const handleMouseMove = (event: MouseEvent) => {
// Get new X and Y positions relative to the container const currentContainerRect = targetContainer!.getBoundingClientRect();
let left = event.clientX - containerRect.left - offsetX;
let top = event.clientY - containerRect.top - offsetY;
// Mouse position relative to container in screen pixels
const mouseX = event.clientX - currentContainerRect.left;
const mouseY = event.clientY - currentContainerRect.top;
// **Restrict movement within container bounds** // Convert to rendered pixel space (undo zoom)
left = Math.max(0, Math.min(left, conScrollWidth - elementRect.width)); const renderedLeft = (mouseX - clickOffsetX) / scale;
top = Math.max(0, Math.min(top, conScrollHeight - elementRect.height)); const renderedTop = (mouseY - clickOffsetY) / scale;
// Convert to original coord space for storage
const originalLeft = renderedLeft / scaleX;
const originalTop = renderedTop / scaleY;
if (groupElements != null && groupElements.length) handleGroupBorderPosition(groupElements, containerRect, offsetX, offsetY); if (groupElements != null && groupElements.length) {
handleGroupBorderPosition(groupElements, currentContainerRect, clickOffsetX, clickOffsetY);
}
// Apply the movement to the element // Update state in original coord space
setDroppedItems( setDroppedItems(
droppedItems.map((item, i) => droppedItems.map((item, i) =>
i == index ? { ...item, dimensions: { ...item.dimensions, left, top }, update: true } : item i === index
? {
...item,
dimensions: {
...item.dimensions,
left: Math.max(0, originalLeft),
top: Math.max(0, originalTop),
},
update: true
}
: item
) )
); );
}; };
const handleMouseUp = () => { const handleMouseUp = () => {
@ -132,7 +150,6 @@ const TagsCon: React.FC<TagsConProps> = ({
document.removeEventListener("pointerup", handleMouseUp); document.removeEventListener("pointerup", handleMouseUp);
}; };
// Add event listeners
document.addEventListener("pointermove", handleMouseMove); document.addEventListener("pointermove", handleMouseMove);
document.addEventListener("pointerup", handleMouseUp); document.addEventListener("pointerup", handleMouseUp);
}; };
@ -308,9 +325,6 @@ const TagsCon: React.FC<TagsConProps> = ({
} }
} }
console.log(signConfObj, saved, "Line 305")
return ( return (
<> <>
{currDocTags.map((item, index) => { {currDocTags.map((item, index) => {

View File

@ -42,6 +42,9 @@ export const droppedTagsDefaultField: DroppedTagsDefaultFieldType = {
} }
interface DocumentContainerProps { interface DocumentContainerProps {
isPages: boolean;
setIsPages: React.Dispatch<React.SetStateAction<boolean>>;
zoomScale: number;
transactionSaving?: boolean; transactionSaving?: boolean;
setTransactionSaving?: React.Dispatch<React.SetStateAction<boolean>>; setTransactionSaving?: React.Dispatch<React.SetStateAction<boolean>>;
isSend?: boolean | null; isSend?: boolean | null;
@ -62,10 +65,11 @@ export interface DocumentContainerRef {
handleTransactionProcess: (isSave?: boolean, isNavigate?: boolean, navigateTo?: string) => void; handleTransactionProcess: (isSave?: boolean, isNavigate?: boolean, navigateTo?: string) => void;
} }
const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProps>( const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProps>(
({ transactionSaving, setTransactionSaving, isSend, isPreview = false, aId = null, tId = null, selectedRecipientIndx = 0 }, ref) => { ({ isPages, zoomScale, transactionSaving, setTransactionSaving, isSend, isPreview = false, aId = null, tId = null, selectedRecipientIndx = 0, setIsPages }, ref) => {
const scaleFactor: number = (zoomScale ?? 100) / 100;
let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS; let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
const dispatch = useDispatch(); const dispatch = useDispatch();
const params = useParams<{ accountId?: string; transactionId?: string; templateId?: string }>(); const params = useParams<{ accountId?: string; transactionId?: string; templateId?: string }>();
@ -93,12 +97,42 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
const tagUndoRef = useRef<DroppedItem[] | []>([]); const tagUndoRef = useRef<DroppedItem[] | []>([]);
const imageRefs = useRef<{ [key: string]: HTMLImageElement | null }>({}); const imageRefs = useRef<{ [key: string]: HTMLImageElement | null }>({});
const [imageSizes, setImageSizes] = useState<Record<string, { width: number; height: number }>>({});
const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients); const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients);
// const selectedDocuments = useSelector((state: RootState) => state.selectedDocuments.selectedDocuments) // const selectedDocuments = useSelector((state: RootState) => state.selectedDocuments.selectedDocuments)
const selectedDocuments = useSelector((state: RootState) => state.documents.documents) const selectedDocuments = useSelector((state: RootState) => state.documents.documents)
// handle Tag - Document Pos and size for Responsiveness - START
const [imageSizes, setImageSizes] = useState<Record<string, { width: number; height: number }>>({});
useEffect(() => {
const observers: ResizeObserver[] = [];
Object.keys(imageRefs.current).forEach((key) => {
const el = imageRefs.current[key];
if (!el) return;
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
setImageSizes((prev) => ({
...prev,
[key]: { width, height },
}));
}
});
observer.observe(el);
observers.push(observer);
});
return () => observers.forEach((o) => o.disconnect());
}, [selectedDocuments]);
// handle Tag - Document Pos and size for Responsiveness - START
// Redux state for dropped items // Redux state for dropped items
const transactionKey = `${accountId}_${transactionId}`; const transactionKey = `${accountId}_${transactionId}`;
const droppedItems = useSelector((state: RootState) => state.documentTags.items[transactionKey] || {}); const droppedItems = useSelector((state: RootState) => state.documentTags.items[transactionKey] || {});
@ -358,8 +392,11 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
clientY = event.clientY; clientY = event.clientY;
} }
let x = clientX - dropZone.left - (dragElemData.width / 2); // let x = (clientX - dropZone.left) - (dragElemData.width / 2);
let y = clientY - dropZone.top - (dragElemData.height / 2); // let y = (clientY - dropZone.top) - (dragElemData.height / 2);
let x = (clientX - dropZone.left) / scaleFactor - (dragElemData.width / 2);
let y = (clientY - dropZone.top) / scaleFactor - (dragElemData.height / 2);
// Handling Drop Over Right // Handling Drop Over Right
// if (dropZone.width < x + dragElemData.width) { // if (dropZone.width < x + dragElemData.width) {
@ -378,8 +415,11 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
// y = 0; // y = 0;
// } // }
x = Math.max(0, Math.min(x, dropZone.width - dragElemData.width)); // x = Math.max(0, Math.min(x, dropZone.width - dragElemData.width));
y = Math.max(0, Math.min(y, dropZone.height - dragElemData.height)); // y = Math.max(0, Math.min(y, dropZone.height - dragElemData.height));
x = Math.max(0, Math.min(x, originalWidth - dragElemData.width));
y = Math.max(0, Math.min(y, originalHeight - dragElemData.height));
const key = `${docId}_${pageIndex}`; const key = `${docId}_${pageIndex}`;
@ -561,8 +601,21 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
return ( return (
<> <>
{/* Center Doc Container - START */} {/* Center Doc Container - START */}
<div id="doc-con" className={`zoom-con col-span-8 bg-slate-100 h-full w-full overflow-auto scroll-smooth`}> <div id="doc-con"
className={`sm:col-span-8 bg-slate-100 h-full w-full overflow-auto scroll-smooth`}>
<div style={{
width: `${100 * scaleFactor}%`, // ← outer wrapper grows to make room
height: `${100 * scaleFactor}%`,
}}>
<div className="zoom-con"
style={{
transform: `scale(${scaleFactor})`,
transformOrigin: 'top left', // ← change to top left
width: `${100 / scaleFactor}%`,
height: scaleFactor < 1
? `${100 / scaleFactor}%` // zoomed out — expand to fill
: 'auto', // zoomed in — let content grow
}}>
{ {
selectedDocuments.length > 0 ? selectedDocuments.map((doc, indxDoc) => { selectedDocuments.length > 0 ? selectedDocuments.map((doc, indxDoc) => {
return ( return (
@ -620,6 +673,7 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
/> />
<DroppedItemsTagsCon <DroppedItemsTagsCon
scaleFactor={scaleFactor}
pageWidth={page.width} pageWidth={page.width}
pageHeight={page.height} pageHeight={page.height}
renderedWidth={imageSizes[key]?.width || imageRefs.current[key]?.offsetWidth || 0} renderedWidth={imageSizes[key]?.width || imageRefs.current[key]?.offsetWidth || 0}
@ -667,8 +721,8 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
) )
}) : <Loader /> }) : <Loader />
} }
</div>
</div>
</div> </div>
@ -695,6 +749,8 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
</> </>
{selectedDocuments && selectedDocuments.length > 0 && <DocumentPages {selectedDocuments && selectedDocuments.length > 0 && <DocumentPages
isPages={isPages}
setIsPages={setIsPages}
droppedItems={droppedItems} droppedItems={droppedItems}
/>} />}

View File

@ -22,6 +22,7 @@ import { DroppedItem } from '../../../utlity/types';
import { setDroppedItems as setDroppedItemsAction } from '../../../store/slices/documentTagsSlice'; import { setDroppedItems as setDroppedItemsAction } from '../../../store/slices/documentTagsSlice';
interface DroppedItemsTagsConProps { interface DroppedItemsTagsConProps {
scaleFactor: number,
pageWidth: number, pageWidth: number,
pageHeight: number, pageHeight: number,
renderedWidth: number, renderedWidth: number,
@ -41,6 +42,7 @@ interface DroppedItemsTagsConProps {
// 👶 Child component with forwardRef // 👶 Child component with forwardRef
const DroppedItemsTagsCon = forwardRef(({ const DroppedItemsTagsCon = forwardRef(({
scaleFactor,
pageWidth, pageWidth,
pageHeight, pageHeight,
renderedWidth, renderedWidth,
@ -163,109 +165,85 @@ const DroppedItemsTagsCon = forwardRef(({
handleRemoveTag handleRemoveTag
})); }));
// HANDLE TAG MOUSE DOWN - START
// Updated handleTagMouseDown function with proper group border positioning
const handleTagMouseDown = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number) => { const handleTagMouseDown = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number) => {
event.preventDefault(); event.preventDefault();
const scale = scaleFactor ?? 1;
const scaleX = renderedWidth && pageWidth ? renderedWidth / pageWidth : 1;
const scaleY = renderedHeight && pageHeight ? renderedHeight / pageHeight : 1;
console.log("=== DRAG DEBUG ===");
console.log("scaleFactor (zoom):", scale);
console.log("renderedWidth:", renderedWidth, "pageWidth:", pageWidth);
console.log("renderedHeight:", renderedHeight, "pageHeight:", pageHeight);
console.log("scaleX (render ratio):", scaleX);
console.log("scaleY (render ratio):", scaleY);
console.log("combined scaleX:", scaleX * scale);
console.log("combined scaleY:", scaleY * scale);
let targetContainer = event?.currentTarget?.parentElement?.parentElement; let targetContainer = event?.currentTarget?.parentElement?.parentElement;
let targetElement = event?.currentTarget?.parentElement; let targetElement = event?.currentTarget?.parentElement;
if (!targetContainer || !targetElement) return; if (!targetContainer || !targetElement) return;
const containerRect = targetContainer.getBoundingClientRect(); // Get container rect in screen space
// const containerRect = targetContainer.getBoundingClientRect();
const elementRect = targetElement.getBoundingClientRect(); const elementRect = targetElement.getBoundingClientRect();
let clientX = event.clientX const containerRect = targetContainer.getBoundingClientRect();
let clientY = event.clientY
if ((event as any).changedTouches?.[0]) { console.log("containerRect:", containerRect.left, containerRect.top, containerRect.width, containerRect.height);
clientX = (event as any).changedTouches[0].pageX; console.log("elementRect:", elementRect.left, elementRect.top, elementRect.width, elementRect.height);
clientY = (event as any).changedTouches[0].pageY; console.log("mouse at:", event.clientX, event.clientY);
}
// Calculate the initial offset (where the mouse was clicked inside the element) // Current tag position in original coord space (from state)
const offsetY = event.clientY - elementRect.top; const currentItem = droppedItems[key][index];
const offsetX = event.clientX - elementRect.left; const tagOriginalLeft = currentItem.dimensions.left;
const tagOriginalTop = currentItem.dimensions.top;
positionRef.current = { left: event.clientX - containerRect.left - offsetX, top: event.clientY - containerRect.top - offsetY }; // Tag's top-left in screen space
const tagScreenLeft = containerRect.left + tagOriginalLeft * scaleX * scale;
const tagScreenTop = containerRect.top + tagOriginalTop * scaleY * scale;
const conScrollWidth = targetContainer?.scrollWidth; // Click offset within tag in screen pixels
const conScrollHeight = targetContainer?.scrollHeight; const clickOffsetX = event.clientX - tagScreenLeft;
const clickOffsetY = event.clientY - tagScreenTop;
let isGroupAttr = event.currentTarget.hasAttribute("group"); let isGroupAttr = event.currentTarget.hasAttribute("group");
let groupId = null; let groupId = null;
if (isGroupAttr) { if (isGroupAttr) groupId = event.currentTarget.getAttribute("group");
groupId = event.currentTarget.getAttribute("group");
}
let groupElements: NodeListOf<HTMLElement> | null = null; let groupElements: NodeListOf<HTMLElement> | null = null;
if (isGroupAttr && groupId) { if (isGroupAttr && groupId) {
groupElements = document.querySelectorAll(`div[group="${groupId}"]`); groupElements = document.querySelectorAll(`div[group="${groupId}"]`);
// Show initial border position during drag start handleGroupBorderPosition(groupElements, containerRect, clickOffsetX, clickOffsetY);
handleGroupBorderPosition(groupElements, containerRect, offsetX, offsetY);
} }
// let timeOut = null
const handleMouseMove = (event: MouseEvent) => { const handleMouseMove = (event: MouseEvent) => {
const currentContainerRect = targetContainer!.getBoundingClientRect();
// AUTO SCROLL ON DRAG - START // Mouse relative to container in screen pixels
// const scrollContainer = document.getElementById("doc-con"); const mouseScreenX = event.clientX - currentContainerRect.left;
const mouseScreenY = event.clientY - currentContainerRect.top;
// const rect = scrollContainer.getBoundingClientRect(); // Subtract click offset to get tag's top-left in screen pixels
const tagScreenX = mouseScreenX - clickOffsetX;
const tagScreenY = mouseScreenY - clickOffsetY;
// const threshold = 80; // distance from edge // Convert screen pixels to original coord space
// const scrollSpeed = 15; // screen = original * scaleX * scale → original = screen / scaleX / scale
const originalLeft = tagScreenX / scale / scaleX;
const originalTop = tagScreenY / scale / scaleY;
// // mouse near bottom positionRef.current = { left: originalLeft, top: originalTop };
// if (event.clientY > rect.bottom - threshold) {
// scrollContainer.scrollTop += scrollSpeed;
// }
// // mouse near top // Set style in rendered pixel space (original * scaleX)
// else if (event.clientY < rect.top + threshold) { // The parent transform:scale handles the rest
// scrollContainer.scrollTop -= scrollSpeed; targetElement!.style.left = `${originalLeft * scaleX}px`;
// } targetElement!.style.top = `${originalTop * scaleY}px`;
// AUTO SCROLL ON DRAG - END
// Get new X and Y positions relative to the container
let clientX = event.clientX
let clientY = event.clientY
if ((event as any).changedTouches?.[0]) {
clientX = (event as any).changedTouches[0].pageX;
clientY = (event as any).changedTouches[0].pageY;
}
let left = clientX - containerRect.left - offsetX;
let top = 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));
// Save in ref (fast, no re-render)
positionRef.current = { left, top };
// Update DOM directly
targetElement!.style.top = `${top}px`;
targetElement!.style.left = `${left}px`;
// Apply the movement to the element first
// setDroppedItems(prevState => ({
// ...prevState,
// [key]: Array.isArray(prevState[key])
// ? prevState[key].map((item, i) =>
// i == index ? { ...item, dimensions: { ...item.dimensions, left, top }, update: true } : item
// )
// : [],
// }));
// Update group border position during drag if it's a group element
if (groupElements != null && groupElements.length) { if (groupElements != null && groupElements.length) {
// Use a small timeout to ensure DOM is updated before recalculating borders
requestAnimationFrame(() => { requestAnimationFrame(() => {
handleGroupBorderPosition(groupElements!, containerRect, offsetX, offsetY); handleGroupBorderPosition(groupElements!, currentContainerRect, clickOffsetX, clickOffsetY);
}); });
} }
}; };
@ -284,33 +262,24 @@ const DroppedItemsTagsCon = forwardRef(({
documentId: "" documentId: ""
} }
// N: Adding pointer event to none get page not tag itselef from elementFromPoint
targetElement.style.pointerEvents = "none"; targetElement.style.pointerEvents = "none";
const el = document.elementFromPoint(e.clientX, e.clientY) as HTMLElement | null;
const el = document.elementFromPoint( const hoveredContainer = el?.closest(".drop-page-in");
e.clientX,
e.clientY
) as HTMLElement | null;
const hoveredContainer = el?.closest(".drop-page-in")
targetElement.style.pointerEvents = "auto"; targetElement.style.pointerEvents = "auto";
// N: Adding pointer event to auto changing back the changes
if (hoveredContainer) { if (hoveredContainer) {
const imgElem: HTMLElement | null = hoveredContainer.querySelector("[data-doc-img]"); const imgElem: HTMLElement | null = hoveredContainer.querySelector("[data-doc-img]");
hoveredDocId = imgElem ? imgElem.getAttribute("id") || "" : "";
hoveredDocId = imgElem ? imgElem.getAttribute("id") || "" : ""
hoveredDocId = hoveredDocId.split("-"); hoveredDocId = hoveredDocId.split("-");
hoveredPageIndex = hoveredDocId[hoveredDocId.length - 1]; hoveredPageIndex = hoveredDocId[hoveredDocId.length - 1];
hoveredDocId.pop() hoveredDocId.pop();
hoveredDocId = hoveredDocId.join("-"); hoveredDocId = hoveredDocId.join("-");
} }
if (hoveredContainer === null || hoveredDocId != currentDocumentId) { // NOT WORKING STILL MOVING THE TAG OUTSIDE THE COTEND DOCUMENT PAGE OVERFLOWING if (hoveredContainer === null || hoveredDocId != currentDocumentId) {
targetElement!.style.top = `${droppedItems[key][index].dimensions.top}px`; // Revert
targetElement!.style.left = `${droppedItems[key][index].dimensions.left}px`; targetElement!.style.left = `${tagOriginalLeft * scaleX}px`;
targetElement!.style.top = `${tagOriginalTop * scaleY}px`;
document.removeEventListener("pointermove", handleMouseMove); document.removeEventListener("pointermove", handleMouseMove);
document.removeEventListener("pointerup", handleMouseUp); document.removeEventListener("pointerup", handleMouseUp);
@ -318,18 +287,11 @@ const DroppedItemsTagsCon = forwardRef(({
} }
if (key in droppedItems && index in droppedItems[key]) { if (key in droppedItems && index in droppedItems[key]) {
// const tag = droppedItems[key][index];
const documentId = droppedItems[key][index]["documentId"]; const documentId = droppedItems[key][index]["documentId"];
const pageIndex = droppedItems[key][index]["pageIndex"]; const pageIndex = droppedItems[key][index]["pageIndex"];
// const targetElemTag = e.target.closest("[data-doc-id]")
tagDocumentUpdate.pageIndex = pageIndex tagDocumentUpdate.pageIndex = pageIndex;
tagDocumentUpdate.documentId = documentId tagDocumentUpdate.documentId = documentId;
// if (targetElemTag) {
// let hoveredDocId = targetElemTag.getAttribute("data-doc-id");
// if (hoveredDocId) {
if (hoveredDocId == documentId) { if (hoveredDocId == documentId) {
if (hoveredPageIndex != pageIndex) { if (hoveredPageIndex != pageIndex) {
@ -341,150 +303,66 @@ const DroppedItemsTagsCon = forwardRef(({
tagDocumentUpdate.updatingDocument = true; tagDocumentUpdate.updatingDocument = true;
tagDocumentUpdate.updatingPage = true; tagDocumentUpdate.updatingPage = true;
tagDocumentUpdate.pageIndex = hoveredPageIndex; tagDocumentUpdate.pageIndex = hoveredPageIndex;
tagDocumentUpdate.documentId = hoveredDocId tagDocumentUpdate.documentId = hoveredDocId;
} }
// }
// }
} }
if (groupElements != null && groupElements.length && groupId) { if (groupElements != null && groupElements.length && groupId) {
// Multiple approaches to ensure the positioning works: setTimeout(() => { handleIGroupBorderPosition(groupId); }, 10);
setTimeout(() => { handleIGroupBorderPosition(groupId); }, 100);
// 1. Immediate update requestAnimationFrame(() => { setTimeout(() => { handleIGroupBorderPosition(groupId); }, 50); });
setTimeout(() => {
handleIGroupBorderPosition(groupId);
}, 10);
// 2. Secondary update to ensure it's properly positioned
setTimeout(() => {
handleIGroupBorderPosition(groupId);
}, 100);
// 3. Final update using requestAnimationFrame for DOM sync
requestAnimationFrame(() => {
setTimeout(() => {
handleIGroupBorderPosition(groupId);
}, 50);
});
} }
// const tempDroppedItems = (droppedItems: any) => ({
// ...droppedItems,
// [key]: Array.isArray(droppedItems[key])
// ? droppedItems[key].map((item, i) =>
// i == index ? { ...item, dimensions: { ...item.dimensions, left, top }, update: true } : item
// )
// : [],
// })
// setDroppedItems(prevState => ({
// ...prevState,
// [key]: Array.isArray(prevState[key])
// ? prevState[key].map((item, i) =>
// i == index ? { ...item, documentId: tagDocumentUpdate.documentId, pageIndex: tagDocumentUpdate.pageIndex, dimensions: { ...item.dimensions, left, top }, update: true } : item
// )
// : [],
// }));
setDroppedItems(prevState => { setDroppedItems(prevState => {
// check documentId match const isSameDocument = currentDocumentId == tagDocumentUpdate.documentId;
const isSameDocument = if (!isSameDocument) return prevState;
currentDocumentId == tagDocumentUpdate.documentId;
if (!isSameDocument) return prevState
// check pageIndex match
const isSamePage = currentPageIndex == tagDocumentUpdate.pageIndex; const isSamePage = currentPageIndex == tagDocumentUpdate.pageIndex;
const scaleX = renderedWidth && pageWidth ? renderedWidth / pageWidth : 1;
const scaleY = renderedHeight && pageHeight ? renderedHeight / pageHeight : 1;
// same document + same page
if (isSameDocument && isSamePage) { if (isSameDocument && isSamePage) {
left = Math.max(0, Math.min(left, conScrollWidth - elementRect.width)); const clampedLeft = Math.max(0, Math.min(left, pageWidth - currentItem.dimensions.width));
top = Math.max(0, Math.min(top, conScrollHeight - elementRect.height)); const clampedTop = Math.max(0, Math.min(top, pageHeight - currentItem.dimensions.height));
const normalizedLeft = left / scaleX;
const normalizedTop = top / scaleY;
return { return {
...prevState, ...prevState,
[key]: Array.isArray(prevState[key]) [key]: Array.isArray(prevState[key])
? prevState[key].map((item, i) => ? prevState[key].map((item, i) =>
i === index i === index ? {
? {
...item, ...item,
documentId:
tagDocumentUpdate.documentId,
pageIndex:
tagDocumentUpdate.pageIndex,
dimensions: {
...item.dimensions,
left: normalizedLeft,
top: normalizedTop,
},
update: true,
}
: item
)
: [],
};
}
// left = left - containerRect.left
const newContainerRect = hoveredContainer?.getBoundingClientRect() as DOMRect;
left = e.clientX - newContainerRect.left - offsetX;
top = e.clientY - newContainerRect.top - offsetY;
left = Math.max(
0,
Math.min(left, newContainerRect.width - elementRect.width)
);
top = Math.max(
0,
Math.min(top, newContainerRect.height - elementRect.height)
);
// move to another page/document
const targetKey = `${tagDocumentUpdate.documentId}_${tagDocumentUpdate.pageIndex}`;
const currentItems = Array.isArray(prevState[key])
? [...prevState[key]]
: [];
const movedItem = currentItems[index];
if (!movedItem) return prevState;
// remove from old array
currentItems.splice(index, 1);
// updated item
const updatedItem = {
...movedItem,
documentId: tagDocumentUpdate.documentId, documentId: tagDocumentUpdate.documentId,
pageIndex: tagDocumentUpdate.pageIndex, pageIndex: tagDocumentUpdate.pageIndex,
dimensions: { dimensions: { ...item.dimensions, left: clampedLeft, top: clampedTop },
...movedItem.dimensions,
left,
top,
},
update: true, update: true,
} : item
) : [],
}; };
}
// Cross page
const newContainerRect = hoveredContainer?.getBoundingClientRect() as DOMRect;
const newMouseScreenX = e.clientX - newContainerRect.left;
const newMouseScreenY = e.clientY - newContainerRect.top;
left = Math.max(0, Math.min(
(newMouseScreenX - clickOffsetX) / scale / scaleX,
pageWidth - currentItem.dimensions.width
));
top = Math.max(0, Math.min(
(newMouseScreenY - clickOffsetY) / scale / scaleY,
pageHeight - currentItem.dimensions.height
));
const targetKey = `${tagDocumentUpdate.documentId}_${tagDocumentUpdate.pageIndex}`;
const currentItems = Array.isArray(prevState[key]) ? [...prevState[key]] : [];
const movedItem = currentItems[index];
if (!movedItem) return prevState;
currentItems.splice(index, 1);
return { return {
...prevState, ...prevState,
// old container updated
[key]: currentItems, [key]: currentItems,
// new container updated
[targetKey]: [ [targetKey]: [
...(Array.isArray(prevState[targetKey]) ...(Array.isArray(prevState[targetKey]) ? prevState[targetKey] : []),
? prevState[targetKey] { ...movedItem, documentId: tagDocumentUpdate.documentId, pageIndex: tagDocumentUpdate.pageIndex, dimensions: { ...movedItem.dimensions, left, top }, update: true },
: []),
updatedItem,
], ],
}; };
}); });
@ -493,15 +371,23 @@ const DroppedItemsTagsCon = forwardRef(({
document.removeEventListener("pointerup", handleMouseUp); document.removeEventListener("pointerup", handleMouseUp);
}; };
// Add event listeners
document.addEventListener("pointermove", handleMouseMove); document.addEventListener("pointermove", handleMouseMove);
document.addEventListener("pointerup", handleMouseUp); document.addEventListener("pointerup", handleMouseUp);
}; };
// HANDLE TAG MOUSE DOWN - END // HANDLE TAG MOUSE DOWN - END
// HANDLE EXPAND TAG - START // HANDLE EXPAND TAG - START
const handleExpandTag = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number) => { const handleExpandTag = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number) => {
event.preventDefault(); // Prevent text selection issues event.preventDefault();
const scale = scaleFactor ?? 1;
const scaleX = renderedWidth && pageWidth ? renderedWidth / pageWidth : 1;
const scaleY = renderedHeight && pageHeight ? renderedHeight / pageHeight : 1;
// Combined scale: zoom * render ratio
const combinedScaleX = scaleX * scale;
const combinedScaleY = scaleY * scale;
const expandingElemIcon = event.currentTarget; const expandingElemIcon = event.currentTarget;
const targetElement = expandingElemIcon.nextElementSibling?.parentElement; const targetElement = expandingElemIcon.nextElementSibling?.parentElement;
@ -514,49 +400,41 @@ const DroppedItemsTagsCon = forwardRef(({
const startX = event.clientX; const startX = event.clientX;
const startY = event.clientY; const startY = event.clientY;
const startWidth = elementRect.width; const startWidth = elementRect.width / combinedScaleX;
const startHeight = elementRect.height; const startHeight = elementRect.height / combinedScaleY;
const handleMouseMove = (event: MouseEvent) => { const handleMouseMove = (event: MouseEvent) => {
const dropZone = targetContainer.getBoundingClientRect();
const deltaX = (event.clientX - startX) / combinedScaleX;
const deltaY = (event.clientY - startY) / combinedScaleY;
const dropZone: DOMRect = targetContainer?.getBoundingClientRect() as DOMRect; let newWidth = startWidth + deltaX;
const droppedElemRect: DOMRect = targetElement?.getBoundingClientRect() as DOMRect; let newHeight = startHeight + deltaY;
console.log(dropZone, "dropZone Line 309") if (newWidth > 400) newWidth = 400;
console.log(droppedElemRect, "rect Line 309") if (newHeight > 200) newHeight = 200;
if (newWidth < 20) newWidth = 20;
if (newHeight < 10) newHeight = 10;
let newWidth = startWidth + (event.clientX - startX); const maxWidth = dropZone.width / combinedScaleX;
let newHeight = startHeight + (event.clientY - startY); const maxHeight = dropZone.height / combinedScaleY;
const currentLeft = droppedItems[key][index].dimensions.left;
const currentTop = droppedItems[key][index].dimensions.top;
if (currentLeft + newWidth > maxWidth) newWidth = maxWidth - currentLeft;
if (currentTop + newHeight > maxHeight) newHeight = maxHeight - currentTop;
if (newHeight > 200 || newWidth > 400) {
if (newHeight > 200) {
newHeight = 200
}
if (newWidth > 400) {
newWidth = 400
}
}
if ((dropZone.right - (droppedElemRect.left + newWidth)) < 0) {
newWidth = startWidth
}
if ((dropZone.bottom - (droppedElemRect.bottom + newHeight)) < 0) {
newHeight = startHeight
}
// Apply the new width & height
setDroppedItems({ setDroppedItems({
...droppedItems, ...droppedItems,
[key]: Array.isArray(droppedItems[key]) // Ensure it's an array [key]: Array.isArray(droppedItems[key])
? droppedItems[key].map((item, i) => ? droppedItems[key].map((item, i) =>
i == index ? { ...item, dimensions: { ...item.dimensions, width: newWidth, height: newHeight }, update: true } : item 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 = () => { const handleMouseUp = () => {

View File

@ -308,7 +308,7 @@ const FieldList: React.FC<FieldListProps> = ({ color, recipientId, recipientIndx
// ); // );
return ( return (
<div className='flex flex-row sm:flex-wrap gap-2 justify-start items-start w-full overflow-x-auto'> <div className='flex flex-row sm:flex-wrap gap-2 justify-start items-start overflow-x-auto w-[90vw] sm:w-full px-2'>
{tagsData.map((field, index) => { {tagsData.map((field, index) => {
const IconComponent = typeToIconMap[field.type] || <FaQuestionCircle />; const IconComponent = typeToIconMap[field.type] || <FaQuestionCircle />;

View File

@ -10,6 +10,7 @@ import { MdKeyboardArrowDown } from 'react-icons/md'
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { fetchDocuments } from '../../../store/slices/documentSlice'; import { fetchDocuments } from '../../../store/slices/documentSlice';
import { FaAngleUp } from 'react-icons/fa'; import { FaAngleUp } from 'react-icons/fa';
import { SlDocs } from "react-icons/sl";
interface LeftSideBarProps { interface LeftSideBarProps {
isFields: boolean; isFields: boolean;
@ -78,16 +79,16 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIn
}, [selectedRecipient]) }, [selectedRecipient])
if (selectedRecipient && selectedRecipient.type == recipientType.CC) { if (selectedRecipient && selectedRecipient.type == recipientType.CC) {
return <div className='col-span-2 bg-white h-full p-4 overflow-y-auto'></div>; return <div className='sm:col-span-2 bg-white h-full p-4 overflow-y-auto'></div>;
} }
return ( return (
<> <>
{/* Left Side Bar - START */} {/* Left Side Bar - START */}
<div className='col-span-2'> <div className='sm:col-span-2'>
{ {
isFields && <div className={`sm:block p-4 absolute sm:top-[0] rounded-tr-md z-50 sm:relative sm:col-span-2 bg-white border-t-1 border-r-1 border-slate-300 sm:h-[calc(100vh-40px)] overflow-y-auto sm:w-auto ${isFields ? "h-[150px] bottom-0 overflow-x-hidden w-full" : "h-[calc(100vh-40px)]"} `}> (isFields || window.innerWidth > 638) && <div className={`sm:block p-4 absolute sm:top-[0] rounded-tr-md z-50 sm:relative sm:col-span-2 bg-white border-t-1 border-r-1 border-slate-300 sm:h-[calc(100vh-40px)] overflow-y-auto sm:w-auto ${(isFields || window.innerWidth > 638) ? "h-[100px] bottom-0 overflow-x-hidden w-full" : "h-[calc(100vh-40px)]"} `}>
{/* <div {/* <div
onClick={() => { onClick={() => {
// handleSetIsPages(false) // handleSetIsPages(false)
@ -114,7 +115,7 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIn
</div> */} </div> */}
{selectedRecipients.length > 0 && isFields && <> {selectedRecipients.length > 0 && (isFields || window.innerWidth > 638) && <>
{/* Fields List and Recipeint - Start */} {/* Fields List and Recipeint - Start */}
{/* Side Bar showing Recipients */} {/* Side Bar showing Recipients */}
<div className='sm:relative sm:top-0 sm:left-0 fixed w-full'> <div className='sm:relative sm:top-0 sm:left-0 fixed w-full'>
@ -177,7 +178,10 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIn
<div <div
id='tags-con-dash-left' id='tags-con-dash-left'
className='mt-2 w-full h-[400px] sm:h-auto'> className='mt-2 w-full h-[400px] sm:h-auto'>
<FieldList color={selectedRecipient?.color || '#2d2d2d'} recipientId={selectedRecipient.id} recipientIndx={selectedRecipientIndx} /> <FieldList
color={selectedRecipient?.color || '#2d2d2d'}
recipientId={selectedRecipient.id}
recipientIndx={selectedRecipientIndx} />
</div> </div>
</div> </div>
@ -195,25 +199,26 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIn
{/* Left Side Pages */} {/* Left Side Pages */}
{<div {<div
onClick={() => { onClick={() => {
// handleSetIsPages(true) handleSetIsPages(true)
handleSetIsFields(false) // handleSetIsFields(false)
}} }}
className='sm:hidden fixed top-[8%] left-0 bg-[var(--primary)] z-50 flex items-center text-white rounded-tr-md rounded-br-md cursor-pointer opacity-50 hover:opacity-100 transition-all duration-300'> className='sm:hidden fixed top-[8%] left-0 bg-[var(--primary)] z-50 flex items-center text-white rounded-tr-sm rounded-br-sm cursor-pointer opacity-50 hover:opacity-100 transition-all duration-300'>
<div className='py-2 pl-2 pr-1'> <div className='py-2 pl-2 pr-2'>
<FaAngleUp className='rotate-90' /> {/* <FaAngleUp className='rotate-90' /> */}
<p className='flex flex-col justify-center items-center text-xs tracking-widest mt-1 pt-1 border-t-1 border-white'> <SlDocs />
{/* <p className='flex flex-col justify-center items-center text-xs tracking-widest mt-1 pt-1 border-t-1 border-white'>
<span>P</span> <span>P</span>
<span>A</span> <span>A</span>
<span>G</span> <span>G</span>
<span>E</span> <span>E</span>
<span>S</span> <span>S</span>
</p> </p> */}
</div> </div>
</div>} </div>}
{!isFields && <div {(!isFields && window.innerWidth <= 638) && <div
className='sm:hidden fixed bottom-0 left-0 rounded-tr-2xl rounded-tl-2xl bg-[var(--primary)] z-50 w-full flex justify-center text-white cursor-pointer opacity-50 hover:opacity-100 transition-all duration-300' className='sm:hidden fixed bottom-0 left-0 rounded-tr-sm rounded-tl-sm bg-[var(--primary)] z-50 w-full flex justify-center text-white cursor-pointer opacity-50 hover:opacity-100 transition-all duration-300'
onClick={() => { onClick={() => {
// handleSetIsPages(false) // handleSetIsPages(false)
handleSetIsFields(true) handleSetIsFields(true)

View File

@ -3,22 +3,18 @@ import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { RootState } from '../../../store/store' import { RootState } from '../../../store/store'
import { FaAngleRight, FaCheck, FaChevronRight, FaPaste, FaRegCopy, FaSave, FaSignature } from 'react-icons/fa' import { FaAngleRight, FaCheck, FaRegCopy, FaSignature } from 'react-icons/fa'
import { IoIosDocument, IoMdArrowRoundBack, IoMdHome } from 'react-icons/io' import { IoIosDocument, IoMdHome } from 'react-icons/io'
import { LuRedo2, LuTableOfContents, LuUndo2 } from 'react-icons/lu' import { LuRedo2, LuUndo2 } from 'react-icons/lu'
import { MdKeyboardArrowDown, MdOutlineKeyboardBackspace } from 'react-icons/md' import { MdKeyboardArrowDown } from 'react-icons/md'
import { RiResetRightLine } from 'react-icons/ri'
import { fetchRecipients, recipientType, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } from '../../../store/slices/selectedRecipientsSlice' import { fetchRecipients, recipientType, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } from '../../../store/slices/selectedRecipientsSlice'
import { fetchDocuments } from '../../../store/slices/documentSlice' import { fetchDocuments } from '../../../store/slices/documentSlice'
import { Link, useNavigate, useParams } from 'react-router-dom' import { useNavigate, useParams } from 'react-router-dom'
import { Bounce, toast } from 'react-toastify'
import { FiChevronDown } from 'react-icons/fi'
import RecipientsCon from '../processStepOne/RecipientsCon' import RecipientsCon from '../processStepOne/RecipientsCon'
import MessageCon from '../processStepOne/MessageCon' import MessageCon from '../processStepOne/MessageCon'
import { IoArrowBackSharp, IoCopy } from 'react-icons/io5' import { IoArrowBackSharp } from 'react-icons/io5'
import { FaRegPaste } from 'react-icons/fa6' import { FaRegPaste } from 'react-icons/fa6'
import ConfirmSendModal from './TransactionSendConfirmationModal' import ConfirmSendModal from './TransactionSendConfirmationModal'
import { RxDividerVertical } from 'react-icons/rx'
import CloseConfirmationModal from './CloseCofirmationModal' import CloseConfirmationModal from './CloseCofirmationModal'
interface NavTwoProps { interface NavTwoProps {
@ -34,6 +30,7 @@ interface NavTwoProps {
aId?: string | null aId?: string | null
tId?: string | null tId?: string | null
onSendClick?: (isSave?: boolean, navigate?: boolean, navigateTo?: string) => void; onSendClick?: (isSave?: boolean, navigate?: boolean, navigateTo?: string) => void;
onZoomChange?: React.Dispatch<React.SetStateAction<number>>
} }
@ -59,20 +56,37 @@ const fitObj = [
label: "200%", label: "200%",
value: "200" value: "200"
}, },
{
label: "250%",
value: "250"
},
{
label: "300%",
value: "300"
},
{
label: "350%",
value: "350"
},
{
label: "400%",
value: "400"
},
] ]
const NavTwo: React.FC<NavTwoProps> = ({ const NavTwo: React.FC<NavTwoProps> = ({
transactionSaving = false, transactionSaving = false,
isFields, // isFields,
setIsFields, // setIsFields,
isPages, // isPages,
setIsPages, // setIsPages,
isSend, // isSend,
setIsSend, // setIsSend,
isPreview = false, isPreview = false,
aId = null, aId = null,
tId = null, tId = null,
onSendClick, onSendClick,
onZoomChange
}) => { }) => {
let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS; let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -181,6 +195,17 @@ const NavTwo: React.FC<NavTwoProps> = ({
return transactionSendValidationObj; return transactionSendValidationObj;
} }
// Remove the zoom() function and the useEffect that calls it
// Instead:
const handleFitSelect = (indx: number) => {
setSelFitIndx(indx);
let value = fitObj[indx].value;
if (value === "FIT") value = "100";
onZoomChange?.(Number(value));
}
function handleSend(isSignSend: boolean = false) { function handleSend(isSignSend: boolean = false) {
// dispatch(showLoader("Loading ...")); // dispatch(showLoader("Loading ..."));
if (onSendClick === undefined) return; if (onSendClick === undefined) return;
@ -215,20 +240,20 @@ const NavTwo: React.FC<NavTwoProps> = ({
if (confirmSendModalOpen.confirm) handleSend() if (confirmSendModalOpen.confirm) handleSend()
}, [confirmSendModalOpen]) }, [confirmSendModalOpen])
const zoom = (percentage: number, contentClass: string) => { // const zoom = (percentage: number, contentClass: string) => {
const content = document.querySelector(`.${contentClass}`); // const content = document.querySelector(`.${contentClass}`);
if (content) { // if (content) {
(content as HTMLElement).style.zoom = `${percentage}%`; // (content as HTMLElement).style.zoom = `${percentage}%`;
} // }
} // }
useEffect(() => { // useEffect(() => {
let indx = fitObj[selFitIndx].value // let indx = fitObj[selFitIndx].value
if (indx == "FIT") { // if (indx == "FIT") {
indx = "100"; // indx = "100";
} // }
zoom(Number(indx), 'zoom-con') // zoom(Number(indx), 'zoom-con')
}, [selFitIndx]) // }, [selFitIndx])
useEffect(() => { useEffect(() => {
if (!transactionSaving) return; if (!transactionSaving) return;
@ -277,7 +302,7 @@ const NavTwo: React.FC<NavTwoProps> = ({
className='hover:text-[var(--primary)] durartion-300 cursor-pointer' className='hover:text-[var(--primary)] durartion-300 cursor-pointer'
onClick={() => setClosePage(true)} onClick={() => setClosePage(true)}
> >
<IoMdHome /> <IoMdHome className='text-[var(--primary)] sm:text-slate-600' />
{/* <img width={120} src="/sme-colored.png" alt="Sign Made Easy" /> */} {/* <img width={120} src="/sme-colored.png" alt="Sign Made Easy" /> */}
</button> </button>
<button <button
@ -288,10 +313,10 @@ const NavTwo: React.FC<NavTwoProps> = ({
</button> </button>
</div> </div>
<div className='w-[1px] h-[28px] bg-slate-200'></div> <div className='w-[0px] md:w-[1px] h-[28px] bg-slate-200'></div>
<div className='flex items-center gap-2 text-xs'> <div className='hidden md:flex items-center gap-2 text-xs'>
<button <button
onClick={() => navigate(-1)} onClick={() => navigate(-1)}
className='underline font-thin cursor-pointer'> className='underline font-thin cursor-pointer'>
@ -306,7 +331,7 @@ const NavTwo: React.FC<NavTwoProps> = ({
<div <div
tabIndex={1} tabIndex={1}
onBlur={() => setIsFitDd(false)} onBlur={() => setIsFitDd(false)}
className='absolute gap-2 flex items-center left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] z-50' className='hidden absolute gap-2 lg:flex items-center left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] z-50'
> >
<div className='flex'> <div className='flex'>
@ -344,7 +369,8 @@ const NavTwo: React.FC<NavTwoProps> = ({
{fitObj.map((val, indx) => { {fitObj.map((val, indx) => {
return (<div return (<div
key={indx} key={indx}
onClick={() => setSelFitIndx(indx)} // onClick={() => setSelFitIndx(indx)}
onClick={() => handleFitSelect(indx)}
className='flex z-50 items-center justify-between gap-2 w-[70px] p-2 text-xs cursor-default hover:bg-slate-100'> className='flex z-50 items-center justify-between gap-2 w-[70px] p-2 text-xs cursor-default hover:bg-slate-100'>
<div <div

View File

@ -38,7 +38,7 @@ const ProcessStepTwo = () => {
const windowWidth = window.innerWidth; const windowWidth = window.innerWidth;
const [isFields, setIsFields] = useState(windowWidth > 638 ? true : false); const [isFields, setIsFields] = useState(windowWidth > 638 ? true : false);
const [isPages, setIsPages] = useState(false); const [isPages, setIsPages] = useState(windowWidth > 638 ? true : false);
const [selectedRecipient, setSelectedRecipient] = useState(0); const [selectedRecipient, setSelectedRecipient] = useState(0);
@ -70,21 +70,13 @@ const ProcessStepTwo = () => {
const handleSetIsFields = (setTo: boolean) => { const handleSetIsFields = (setTo: boolean) => {
if (setTo != undefined && setTo != isFields) { setIsFields(true)
setIsFields(setTo)
if (setTo == true) {
setIsPages(false) setIsPages(false)
} }
};
}
const handleSetIsPages = (setTo: boolean) => { const handleSetIsPages = (setTo: boolean) => {
if (setTo != undefined && setTo != isPages) {
setIsPages(setTo)
if (setTo == true) {
setIsFields(false) setIsFields(false)
} setIsPages(true)
};
} }
const docRef = useRef<DocumentContainerRef>(null); const docRef = useRef<DocumentContainerRef>(null);
@ -93,13 +85,59 @@ const ProcessStepTwo = () => {
docRef.current?.handleTransactionProcess(isSave, isNavigate, navigateTo); docRef.current?.handleTransactionProcess(isSave, isNavigate, navigateTo);
}; };
const [zoomScale, setZoomScale] = useState<number>(100); // percentage
// To center the container on every zoom scale change - START
useEffect(() => {
const container = document.getElementById("doc-con");
if (!container) return;
const prevScrollRatio = container.scrollTop / (container.scrollHeight || 1);
const newScrollTop = prevScrollRatio * container.scrollHeight;
const newScrollLeft = (container.scrollWidth - container.clientWidth) / 2;
container.scrollTo({
top: newScrollTop,
left: newScrollLeft,
behavior: "instant" as ScrollBehavior
});
}, [zoomScale]);
// To center the container on every zoom scale change - END
return ( return (
<div className={`min-w-full relative h-screen bg-slate-100 overflow-hidden ${(globalLoader.isLoading) ? 'pointer-events-none' : ''}`}> <div className={`min-w-full relative h-screen bg-slate-100 overflow-hidden ${(globalLoader.isLoading) ? 'pointer-events-none' : ''}`}>
<NavTwo transactionSaving={transactionSaving} isFields={isFields} isPages={isPages} setIsPages={setIsPages} setIsFields={setIsFields} isSend={isSend} setIsSend={setIsSend} onSendClick={handleSendClick} /> <NavTwo
onZoomChange={setZoomScale}
transactionSaving={transactionSaving}
// isFields={isFields}
// isPages={isPages}
// setIsPages={setIsPages}
// setIsFields={setIsFields}
// isSend={isSend}
// setIsSend={setIsSend}
onSendClick={handleSendClick} />
<div className='sm:grid sm:grid-cols-12 sm:gap-1 mt-1 h-[calc(100vh-46px)] overflow-hidden'> <div className='sm:grid sm:grid-cols-12 sm:gap-1 mt-1 h-[calc(100vh-46px)] overflow-hidden'>
<LeftSideBar isFields={isFields} isPages={isPages} selectedRecipientIndx={selectedRecipient} setSelectedRecipient={setSelectedRecipient} handleSetIsFields={handleSetIsFields} handleSetIsPages={handleSetIsPages} /> <LeftSideBar
<DocumentContainer ref={docRef} selectedRecipientIndx={selectedRecipient} transactionSaving={transactionSaving} setTransactionSaving={setTransactionSaving} isSend={isSend} setIsSend={setIsSend} /> isFields={isFields}
isPages={isPages}
selectedRecipientIndx={selectedRecipient}
setSelectedRecipient={setSelectedRecipient}
handleSetIsFields={handleSetIsFields}
handleSetIsPages={handleSetIsPages}
/>
<DocumentContainer
isPages={isPages}
setIsPages={setIsPages}
ref={docRef}
zoomScale={zoomScale}
selectedRecipientIndx={selectedRecipient}
transactionSaving={transactionSaving}
setTransactionSaving={setTransactionSaving}
isSend={isSend}
setIsSend={setIsSend}
/>
</div> </div>
{globalLoader.isLoading && <Loader />} {globalLoader.isLoading && <Loader />}

View File

@ -6,7 +6,11 @@ import { DroppedItemsObj } from '../DocumentContainer';
import { MdKeyboardArrowUp, MdMoreVert } from 'react-icons/md'; import { MdKeyboardArrowUp, MdMoreVert } from 'react-icons/md';
import { PiTagSimpleFill } from 'react-icons/pi'; import { PiTagSimpleFill } from 'react-icons/pi';
const DocumentPages: React.FC<{ droppedItems: DroppedItemsObj }> = ({ droppedItems }) => { const DocumentPages: React.FC<{
isPages: boolean;
droppedItems: DroppedItemsObj;
setIsPages: React.Dispatch<React.SetStateAction<boolean>>;
}> = ({ isPages, droppedItems, setIsPages }) => {
type RecipientPageTagInfo = { type RecipientPageTagInfo = {
color: string; color: string;
@ -132,8 +136,22 @@ const DocumentPages: React.FC<{ droppedItems: DroppedItemsObj }> = ({ droppedIte
}, [droppedItems]); }, [droppedItems]);
useEffect(() => {
setTimeout(() => {
if (!isPages) return
const docPagesCon = document.getElementById("document-pages");
if (docPagesCon) {
docPagesCon.focus();
}
}, 0)
}, [isPages]);
return ( return (
<div className="col-span-2 flex flex-col w-full overflow-y-auto"> <div id='document-pages'
tabIndex={-1}
onBlur={() => window.innerWidth <= 638 && setIsPages(false)}
className={`h-[100vh] absolute ${isPages || window.innerWidth > 638 ? "" : "hidden"} top-0 left-0 sm:static max-w-max bg-slate-100 z-50 sm:col-span-2 flex flex-col w-full overflow-y-auto`}>
{selectedDocuments.map((document, indx) => ( {selectedDocuments.map((document, indx) => (
<div key={indx} className="border-b border-slate-200 pb-3 mb-1"> <div key={indx} className="border-b border-slate-200 pb-3 mb-1">

View File

@ -49,6 +49,7 @@ interface Signature {
} }
interface ClientSignConProps { interface ClientSignConProps {
zoomScale: number,
setOpenSign?: (value: boolean) => void; setOpenSign?: (value: boolean) => void;
getAllSignatures: () => Promise<void>; getAllSignatures: () => Promise<void>;
// editingSignature?: Signature | null; // editingSignature?: Signature | null;
@ -90,6 +91,7 @@ const fontsMap = [
const ClientSignCon: React.FC<ClientSignConProps> = ({ const ClientSignCon: React.FC<ClientSignConProps> = ({
zoomScale,
recipient, recipient,
setOpenSign, setOpenSign,
recipientId, recipientId,
@ -98,7 +100,8 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
signTagId signTagId
}) => { }) => {
console.log("savedModal", recipient) // const scaleFactor: number = (zoomScale ?? 100) / 100;
const [activeTab, setActiveTab] = useState<'SELECT' | 'DRAW' | 'UPLOAD' | 'SAVED'>('DRAW'); const [activeTab, setActiveTab] = useState<'SELECT' | 'DRAW' | 'UPLOAD' | 'SAVED'>('DRAW');
const [fullName, setFullName] = useState(''); const [fullName, setFullName] = useState('');
const [initials, setInitials] = useState(''); const [initials, setInitials] = useState('');

View File

@ -9,6 +9,7 @@ export default defineConfig({
server: { server: {
port: 5173, port: 5173,
allowedHosts: [ allowedHosts: [
'smashing-albacore-engaging.ngrok-free.app',
'quality-friendly-goldfish.ngrok-free.app', 'quality-friendly-goldfish.ngrok-free.app',
'officially-powerful-goat.ngrok-free.app', 'officially-powerful-goat.ngrok-free.app',
'89f0-49-36-189-120.ngrok-free.app', '89f0-49-36-189-120.ngrok-free.app',