Last Meeting Changes - Document page change according to scroll

This commit is contained in:
Rohit Singh 2026-07-13 15:27:03 +05:30
parent c8bba1d34f
commit 78f895bd2c
3 changed files with 144 additions and 41 deletions

View File

@ -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 { MdKeyboardArrowUp, MdMoreVert } from 'react-icons/md';
import { DocumentType } from './SignDocument'; import { DocumentType } from './SignDocument';
import { DroppedItem } from '../../utlity/types'; import { DroppedItem } from '../../utlity/types';
@ -9,25 +9,35 @@ interface DocumentPagesInterface {
setDropDownTogglePage: React.Dispatch<React.SetStateAction<string>>; setDropDownTogglePage: React.Dispatch<React.SetStateAction<string>>;
dropDownTogglePage: string; dropDownTogglePage: string;
droppedItems: Record<string, DroppedItem[]> | null; droppedItems: Record<string, DroppedItem[]> | null;
isPages: boolean;
setIsPages: React.Dispatch<React.SetStateAction<boolean>>;
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<string | null>( // const [activePage, setActivePage] = useState<string | null>(
selectedDocuments?.[0] ? `${selectedDocuments[0].documentId}_0` : null // selectedDocuments?.[0] ? `${selectedDocuments[0].documentId}_0` : null
) // )
const [documentPagesTagsMapping, setDocumentPagesTagsMapping] = useState<Record<string, Record<number, [number, string]>>>({}) const [documentPagesTagsMapping, setDocumentPagesTagsMapping] = useState<Record<string, Record<number, [number, string]>>>({})
useEffect(() => { // useEffect(() => {
const documentId = selectedDocuments?.[0] ? `${selectedDocuments[0].documentId}_0` : null // const documentId = selectedDocuments?.[0] ? `${selectedDocuments[0].documentId}_0` : null
if (documentId === null) return; // if (documentId === null) return;
const key = 'pageDropDown' + 0; // const key = 'pageDropDown' + 0;
setDropDownTogglePage(dropDownTogglePage === key ? '' : key); // setDropDownTogglePage(dropDownTogglePage === key ? '' : key);
setActivePage(`${documentId}_0`); // setActivePage(`${documentId}_0`);
window.location.hash = documentId + "0"; // window.location.hash = documentId + "0";
}, [selectedDocuments]) // }, [selectedDocuments])
// const goToPage = (docId: string, pageIndex: number) => {
// const key = `${docId}_${pageIndex}`;
// onPageSelect(key);
// };
const [openDocs, setOpenDocs] = useState<Record<string, boolean>>( const [openDocs, setOpenDocs] = useState<Record<string, boolean>>(
() => () =>
@ -54,11 +64,18 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl
}, [selectedDocuments]) }, [selectedDocuments])
const goToPage = (documentId: string, pageIndex: number) => { const goToPage = (documentId: string, pageIndex: number) => {
console.log("boom Active Page:", activePage);
const key = `${documentId}_${pageIndex}`; const key = `${documentId}_${pageIndex}`;
setActivePage(key); onPageSelect(key);
window.location.hash = documentId + "" + pageIndex;
} }
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(() => { useEffect(() => {
setTimeout(() => { setTimeout(() => {
@ -87,7 +104,9 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl
// const isOpen = dropDownTogglePage === 'pageDropDown' + indx; // const isOpen = dropDownTogglePage === 'pageDropDown' + indx;
return ( return (
<div key={indx} className="border-b border-slate-200"> <div
key={indx}
className="border-b border-slate-200">
{/* Document Header */} {/* Document Header */}
<div <div
@ -118,6 +137,7 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl
return ( return (
<div <div
key={pageIndex} key={pageIndex}
ref={(el) => { thumbnailRefs.current[key] = el; }} // NEW
onClick={() => goToPage(document.documentId, pageIndex)} 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" className="flex flex-col items-center py-2 px-1 rounded-md cursor-pointer hover:bg-slate-50 transition-colors"
> >

View File

@ -172,6 +172,8 @@ enum RightSideNav {
PAGES = "pages" PAGES = "pages"
} }
let selectedDoc: { [key: string]: string | null } = { key: null }
const SignDocument = () => { const SignDocument = () => {
const windowWidth = window.innerWidth; const windowWidth = window.innerWidth;
@ -179,6 +181,7 @@ const SignDocument = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const imageRefs = useRef<{ [key: string]: HTMLImageElement | null }>({}); const imageRefs = useRef<{ [key: string]: HTMLImageElement | null }>({});
const pageRefs = useRef<{ [key: string]: HTMLDivElement | null }>({});
// Signer data - START // Signer data - START
const [selectedDocuments, setSelectedDocuments] = useState<DocumentType[] | null>(null); const [selectedDocuments, setSelectedDocuments] = useState<DocumentType[] | null>(null);
@ -187,6 +190,8 @@ const SignDocument = () => {
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);
const [activePage, setActivePage] = useState<string | null>(null);
const isProgrammaticScrollRef = useRef(false);
const [recipient, setRecipient] = useState<RecipientType | null>(null); const [recipient, setRecipient] = useState<RecipientType | null>(null);
const [tagJson, setTagJson] = useState<DroppedItem[] | []>([]); const [tagJson, setTagJson] = useState<DroppedItem[] | []>([]);
@ -252,6 +257,56 @@ const SignDocument = () => {
}; };
// Handle Confirm Sign Modal - END // 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<string, number> = {};
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 // To center the container on every zoom scale change - START
useEffect(() => { useEffect(() => {
@ -272,30 +327,30 @@ const SignDocument = () => {
// To center the container on every zoom scale change - END // To center the container on every zoom scale change - END
// Handle Tags and Image Size - START // Handle Tags and Image Size - START
useEffect(() => { // useEffect(() => {
const observers: ResizeObserver[] = []; // const observers: ResizeObserver[] = [];
Object.keys(imageRefs.current).forEach((key) => { // Object.keys(imageRefs.current).forEach((key) => {
const el = imageRefs.current[key]; // const el = imageRefs.current[key];
if (!el) return; // if (!el) return;
const observer = new ResizeObserver((entries) => { // const observer = new ResizeObserver((entries) => {
for (const entry of entries) { // for (const entry of entries) {
const { width, height } = entry.contentRect; // const { width, height } = entry.contentRect;
setImageSizes((prev) => ({ // setImageSizes((prev) => ({
...prev, // ...prev,
[key]: { width, height }, // [key]: { width, height },
})); // }));
} // }
}); // });
observer.observe(el); // observer.observe(el);
observers.push(observer); // 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 // Handle Tags anf Image Size = END
@ -345,6 +400,16 @@ const SignDocument = () => {
setSelectedTag({ key, index }) 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(() => { useEffect(() => {
dispatch(showLoader("Loading ...")); dispatch(showLoader("Loading ..."));
@ -958,6 +1023,10 @@ const SignDocument = () => {
// } // }
// } // }
const handleDocumentFocus = (key: string) => {
selectedDoc.key = key;
};
return ( return (
<> <>
<div className={`w-full overflow-hidden relative ${(globalLoader.isLoading) ? 'pointer-events-none overflow-hidden' : ''}`}> <div className={`w-full overflow-hidden relative ${(globalLoader.isLoading) ? 'pointer-events-none overflow-hidden' : ''}`}>
@ -1110,7 +1179,7 @@ const SignDocument = () => {
</div> </div>
{/* Right Side - ENDS */} {/* Right Side - ENDS */}
@ -1200,7 +1269,10 @@ const SignDocument = () => {
return ( return (
<div <div
key={indx} key={indx}
onFocus={() => handleDocumentFocus(key)}
id={doc.documentId + indx} id={doc.documentId + indx}
data-page-key={key}
ref={(el) => { pageRefs.current[key] = el; }}
onDrop={(e) => handleDrop(e, doc.documentId, doc.documentId, page.index)} onDrop={(e) => handleDrop(e, doc.documentId, doc.documentId, page.index)}
onDragOver={(e) => handleDragOver(e)} onDragOver={(e) => handleDragOver(e)}
className='drop-page-in shadow-md relative overflow-hidden' className='drop-page-in shadow-md relative overflow-hidden'
@ -1383,10 +1455,24 @@ const SignDocument = () => {
{!transparentTags && isFields && recipientId && provideTags && recipient?.type != "CC" && <FieldList color={"#8f87f1"} recipientId={recipientId} recipientIndx={0} acntId={"1"} />} {!transparentTags && isFields && recipientId && provideTags && recipient?.type != "CC" && <FieldList color={"#8f87f1"} recipientId={recipientId} recipientIndx={0} acntId={"1"} />}
<DocumentPages <DocumentPages
isPages={isPages}
setIsPages={setIsPages}
droppedItems={droppedItems}
selectedDocuments={selectedDocuments} selectedDocuments={selectedDocuments}
setDropDownTogglePage={setDropDownTogglePage} setDropDownTogglePage={setDropDownTogglePage}
dropDownTogglePage={dropDownTogglePage} dropDownTogglePage={dropDownTogglePage}
droppedItems={droppedItems}
activePage={activePage}
onPageSelect={(key: string) => { // 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);
}}
/> />
</div> </div>

View File

@ -62,7 +62,7 @@ const DocumentPages: React.FC<{
const thumbnailRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); // NEW const thumbnailRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); // NEW
useEffect(() => { // NEW useEffect(() => {
if (!activePage) return; if (!activePage) return;
const el = thumbnailRefs.current[activePage]; const el = thumbnailRefs.current[activePage];
el?.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); el?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
@ -101,9 +101,6 @@ const DocumentPages: React.FC<{
// // }, [droppedItems, selectedRecipientIndx]) // // }, [droppedItems, selectedRecipientIndx])
// }, [droppedItems]) // }, [droppedItems])
useEffect(() => { useEffect(() => {
setTimeout(() => { setTimeout(() => {