From 78f895bd2c42c660ea379c8cffbcac209477421a Mon Sep 17 00:00:00 2001 From: Rohit Singh Date: Mon, 13 Jul 2026 15:27:03 +0530 Subject: [PATCH] Last Meeting Changes - Document page change according to scroll --- src/components/client/DocumentPages.tsx | 52 ++++--- src/components/client/SignDocument.tsx | 128 +++++++++++++++--- .../documentPages/DocumentPages.tsx | 5 +- 3 files changed, 144 insertions(+), 41 deletions(-) diff --git a/src/components/client/DocumentPages.tsx b/src/components/client/DocumentPages.tsx index e920456..542e4be 100644 --- a/src/components/client/DocumentPages.tsx +++ b/src/components/client/DocumentPages.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import { MdKeyboardArrowUp, MdMoreVert } from 'react-icons/md'; import { DocumentType } from './SignDocument'; import { DroppedItem } from '../../utlity/types'; @@ -9,25 +9,35 @@ interface DocumentPagesInterface { setDropDownTogglePage: React.Dispatch>; dropDownTogglePage: string; droppedItems: Record | null; + isPages: boolean; + setIsPages: React.Dispatch>; + activePage: string | null; // NEW + onPageSelect: (key: string) => void; } -const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownTogglePage, droppedItems }: DocumentPagesInterface) => { +const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownTogglePage, droppedItems, isPages, setIsPages, activePage, onPageSelect }: DocumentPagesInterface) => { - const [activePage, setActivePage] = useState( - selectedDocuments?.[0] ? `${selectedDocuments[0].documentId}_0` : null - ) + // const [activePage, setActivePage] = useState( + // selectedDocuments?.[0] ? `${selectedDocuments[0].documentId}_0` : null + // ) const [documentPagesTagsMapping, setDocumentPagesTagsMapping] = useState>>({}) - useEffect(() => { - const documentId = selectedDocuments?.[0] ? `${selectedDocuments[0].documentId}_0` : null - if (documentId === null) return; + // useEffect(() => { + // const documentId = selectedDocuments?.[0] ? `${selectedDocuments[0].documentId}_0` : null + // if (documentId === null) return; - const key = 'pageDropDown' + 0; - setDropDownTogglePage(dropDownTogglePage === key ? '' : key); - setActivePage(`${documentId}_0`); - window.location.hash = documentId + "0"; - }, [selectedDocuments]) + // const key = 'pageDropDown' + 0; + // setDropDownTogglePage(dropDownTogglePage === key ? '' : key); + // setActivePage(`${documentId}_0`); + // window.location.hash = documentId + "0"; + // }, [selectedDocuments]) + + + // const goToPage = (docId: string, pageIndex: number) => { + // const key = `${docId}_${pageIndex}`; + // onPageSelect(key); + // }; const [openDocs, setOpenDocs] = useState>( () => @@ -54,11 +64,18 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl }, [selectedDocuments]) const goToPage = (documentId: string, pageIndex: number) => { + console.log("boom Active Page:", activePage); const key = `${documentId}_${pageIndex}`; - setActivePage(key); - window.location.hash = documentId + "" + pageIndex; + onPageSelect(key); } + const thumbnailRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); // NEW + + useEffect(() => { + if (!activePage) return; + const el = thumbnailRefs.current[activePage]; + el?.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + }, [activePage]); useEffect(() => { setTimeout(() => { @@ -87,7 +104,9 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl // const isOpen = dropDownTogglePage === 'pageDropDown' + indx; return ( -
+
{/* Document Header */}
{ thumbnailRefs.current[key] = el; }} // NEW onClick={() => goToPage(document.documentId, pageIndex)} className="flex flex-col items-center py-2 px-1 rounded-md cursor-pointer hover:bg-slate-50 transition-colors" > diff --git a/src/components/client/SignDocument.tsx b/src/components/client/SignDocument.tsx index 628c633..94f3af7 100644 --- a/src/components/client/SignDocument.tsx +++ b/src/components/client/SignDocument.tsx @@ -172,6 +172,8 @@ enum RightSideNav { PAGES = "pages" } +let selectedDoc: { [key: string]: string | null } = { key: null } + const SignDocument = () => { const windowWidth = window.innerWidth; @@ -179,6 +181,7 @@ const SignDocument = () => { const dispatch = useDispatch(); const imageRefs = useRef<{ [key: string]: HTMLImageElement | null }>({}); + const pageRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); // Signer data - START const [selectedDocuments, setSelectedDocuments] = useState(null); @@ -187,6 +190,8 @@ const SignDocument = () => { const [selectedTag, setSelectedTag] = useState<{ key: string, index: number } | null>(null); const [isPages, setIsPages] = useState(true); + const [activePage, setActivePage] = useState(null); + const isProgrammaticScrollRef = useRef(false); const [recipient, setRecipient] = useState(null); const [tagJson, setTagJson] = useState([]); @@ -252,6 +257,56 @@ const SignDocument = () => { }; // Handle Confirm Sign Modal - END + // Sync activePage with scroll position via IntersectionObserver + useEffect(() => { + const scrollRoot = document.getElementById('doc-con'); + if (!scrollRoot) return; + + // Track intersection ratio per page so we can pick the most-visible one + // when multiple pages are partially visible at once. + const ratios: Record = {}; + + const observer = new IntersectionObserver( + (entries) => { + // Ignore intersection changes caused by our own programmatic + // scroll (e.g. clicking a thumbnail) to avoid feedback loops. + if (isProgrammaticScrollRef.current) return; + + entries.forEach((entry) => { + const key = entry.target.getAttribute('data-page-key'); + if (!key) return; + ratios[key] = entry.isIntersecting ? entry.intersectionRatio : 0; + }); + + // Pick the key with the highest intersection ratio + let bestKey: string | null = null; + let bestRatio = 0; + for (const [key, ratio] of Object.entries(ratios)) { + if (ratio > bestRatio) { + bestRatio = ratio; + bestKey = key; + } + } + + if (bestKey && bestRatio > 0) { + setActivePage((prev) => (prev === bestKey ? prev : bestKey)); // dedupe, avoid re-render + } + }, + { + root: scrollRoot, + // Multiple thresholds so ratio updates fire smoothly while + // scrolling, without needing scroll-event listeners at all. + threshold: [0, 0.25, 0.5, 0.75, 1], + rootMargin: '0px', + } + ); + + Object.values(pageRefs.current).forEach((el) => { + if (el) observer.observe(el); + }); + + return () => observer.disconnect(); + }, [selectedDocuments]); // To center the container on every zoom scale change - START useEffect(() => { @@ -272,30 +327,30 @@ const SignDocument = () => { // To center the container on every zoom scale change - END // Handle Tags and Image Size - START - useEffect(() => { - const observers: ResizeObserver[] = []; + // useEffect(() => { + // const observers: ResizeObserver[] = []; - Object.keys(imageRefs.current).forEach((key) => { - const el = imageRefs.current[key]; - if (!el) return; + // 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 }, - })); - } - }); + // 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); - }); + // observer.observe(el); + // observers.push(observer); + // }); - return () => observers.forEach((o) => o.disconnect()); + // return () => observers.forEach((o) => o.disconnect()); - }, [selectedDocuments]); // re-run when documents load + // }, [selectedDocuments]); // re-run when documents load // Handle Tags anf Image Size = END @@ -345,6 +400,16 @@ const SignDocument = () => { setSelectedTag({ key, index }) } + useEffect(() => { + if (selectedDocuments && selectedDocuments.length > 0 && selectedDocuments[0].pages?.length > 0) { + const firstDoc = selectedDocuments[0]; + const firstPage = firstDoc.pages[0]; + if (!selectedDoc.key) { + selectedDoc.key = `${firstDoc.documentId}_${firstPage.index}`; + } + } + }, [selectedDocuments]); + useEffect(() => { dispatch(showLoader("Loading ...")); @@ -958,6 +1023,10 @@ const SignDocument = () => { // } // } + const handleDocumentFocus = (key: string) => { + selectedDoc.key = key; + }; + return ( <>
@@ -1110,7 +1179,7 @@ const SignDocument = () => { - +
{/* Right Side - ENDS */} @@ -1200,7 +1269,10 @@ const SignDocument = () => { return (
handleDocumentFocus(key)} id={doc.documentId + indx} + data-page-key={key} + ref={(el) => { pageRefs.current[key] = el; }} onDrop={(e) => handleDrop(e, doc.documentId, doc.documentId, page.index)} onDragOver={(e) => handleDragOver(e)} className='drop-page-in shadow-md relative overflow-hidden' @@ -1383,10 +1455,24 @@ const SignDocument = () => { {!transparentTags && isFields && recipientId && provideTags && recipient?.type != "CC" && } { // NEW — replaces hash-based nav + isProgrammaticScrollRef.current = true; + setActivePage(key); + pageRefs.current[key]?.scrollIntoView({ behavior: 'smooth', block: 'start' }); + // Release the guard after the smooth scroll has had time to settle, + // so the IntersectionObserver resumes driving activePage afterward. + window.setTimeout(() => { isProgrammaticScrollRef.current = false; }, 600); + }} />
diff --git a/src/components/process/processStepTwo/documentPages/DocumentPages.tsx b/src/components/process/processStepTwo/documentPages/DocumentPages.tsx index 7738b47..e602724 100644 --- a/src/components/process/processStepTwo/documentPages/DocumentPages.tsx +++ b/src/components/process/processStepTwo/documentPages/DocumentPages.tsx @@ -62,7 +62,7 @@ const DocumentPages: React.FC<{ const thumbnailRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); // NEW - useEffect(() => { // NEW + useEffect(() => { if (!activePage) return; const el = thumbnailRefs.current[activePage]; el?.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); @@ -101,9 +101,6 @@ const DocumentPages: React.FC<{ // // }, [droppedItems, selectedRecipientIndx]) // }, [droppedItems]) - - - useEffect(() => { setTimeout(() => {